当前位置: 首页 > 科技观察

MySQL不香吗,为什么还要Elasticsearch?_0

时间:2023-03-15 17:27:12 科技观察

近年来,公司业务快速发展,数据量呈爆发式增长,随之而来的是海量数据查询带来的挑战。秒级甚至毫秒级速度回传。这显然离不开搜索引擎的帮助。在搜索引擎中,ES(ElasticSearch)无疑是其中的佼佼者。多年来一直位居DBRanking搜索引擎评价榜首,也是大多数大公司的选择。首选。图片来自Pexels,那么它和MySQL等传统DB相比有什么优势,ES数据是如何生成的,数据达到PB时如何保证ES索引数据的实时性更好的满足业务需求呢布。本文将结合我司在ES上的实践经验,和大家谈谈如何构建准实时索引的一些想法,希望对大家有所启发。本文内容如下:为什么要用搜索引擎,是MySQL不好吗?ES索引数据构建PB级ES准实时索引数据构建方法为什么要用搜索引擎,MySQL不行?,只适用于海量数据存储,无法应对海量数据下各种复杂条件下的查询。有人说加索引可以避免全表扫描,提高查询速度。为什么说不适合海量数据查询呢?原因有二:①添加索引确实可以提高查询速度,但是MySQL中添加多个索引最终会在执行SQL时,只会选择成本最低的索引。如果没有索引满足搜索条件,就会触发全表扫描,即使使用复合索引,也必须遵守最左前缀原则才能命中索引。但是在海量数据的各种查询条件下,很可能不满足最左前缀原则导致索引失效,我们知道存储是需要成本的。如果你为每种情况添加索引,以innoDB为例,每添加一个索引,就会创建一个B+树。如果是海量数据,会大大增加存储成本。有人反映他们公司一个表的实际内容大小只有10G,但是索引大小却有30G!多么巨大的代价!所以我不认为索引越多越好。②有些查询条件不能通过给MySQL加索引来解决。比如我要查询产品中所有标题中带有“格力空调”的关键词,如果用MySQL写,就会写如下代码:SELECT*FROMproductWHEREtitlelike'%Greeairconditioner%'can't命中任何一个索引,都会触发全表扫描,你不能指望每个人都丢掉他想要的产品,人是会犯错误的,我们经常会把“格力空调”当成“格子空间”这样的错误。那么SQL语句就会变成:SELECT*FROMproductWHEREtitlelike'%gridconditioner%'这种情况下,即使触发了全表扫描,也查询不到任何商品。综上所述,MySQL的查询能力确实有限。ES介绍与其说上面列举的几点是MySQL的不足,倒不如说MySQL本身并不是为海量数据查询而设计的。科技行业有专攻,海量数据查询需要专用的搜索引擎,其中ES是当之无愧的王者。是一个基于Lucene引擎的开源分布式搜索分析引擎,可以对PB数据提供近实时的查询,广泛应用于全文搜索、日志分析、监控分析等场景。主要有以下三个特点:轻松支持各种复杂的查询条件:是一个分布式实时文件存储,会对每个字段进行索引(倒排索引),使用高效的倒排索引,自定义打分、排序能力和丰富的分词插件等,可满足任何复杂查询条件下的全文检索需求。扩展性强:天然支持分布式存储,通过极其简单的配置实现数百或数千台服务器的分布式水平扩展,轻松处理PB级结构化或非结构化数据。高可用、容灾性能好:通过主备节点的使用,以及故障的自动检测和恢复,有效保证了高可用。我们先以类比MySQL的形式来理解ES的一些重要概念:通过类比不难看出ES的以下几个概念:MySQL的数据库(DataBase)相当于索引(index),一个数据的逻辑集合,ES的主要工作是创建索引和查询索引。一个数据库中会有多个表,同一个Index也会有多个类型。一个表会有多个行(Row),同一个Type也会有多个Documents。Schema规定了表名、表字段、是否建立索引等。同样,Mapping也规定了Type字段的处理规则,即如何建立索引、是否分词、分词规则等。在MySQL中,需要手动创建索引,而在ES中,所有的字段都可以被索引,只要在Mapping中指定即可。那么为什么ES中的索引效率如此之高,在海量数据下可以做到秒级的结果呢?它使用了多种优化方法,最主要的原因是它使用了一种叫做倒排索引的方法来生成索引,以避免Scannedalldocuments。那么什么是倒排索引呢?通过文档查找关键词等数据我们称之为正索引,通过关键词查找文档我们称之为倒排索引。假设有以下三个文档(Document):要查找其中包含comming的文档,如果要排序索引,则需要取出每个文档的内容,看看是否有这个词,这将肯定会导致全表扫描,你怎么用倒排索引来查找呢?它会先对每个文档的内容进行分片,小写等,然后在每个分片和包含该分片的文档之间建立映射关系。如果有多个文档包含这个分词,文档会根据重要性,即文档的权重进行排序(通常使用TF-IDF对文档进行打分)。所以我们可以得到如下关系:在这种情况下,如果我们想找到所有带有comming的文档,我们只需要检查一次,而且在这种情况下,查询多个单词的性能也很好,只要我们查询多个条件对应的文档列表,然后取交集,大大提高了查询效率。画外音:这里简化了一些流程。其实首先要根据词表来定位词。但是,这些过程非常快,可以忽略不计。有兴趣的读者可以参考相关资料了解更多。除了倒排索引,ES的分布式架构也天然适用于海量数据查询。我们来看看ES的架构:一个ES集群由多个node节点组成,每个索引基于分片(Shard,indexsubset),数据存在于多个node节点上。这样当一个查询请求进来时,在各个节点上查询相应的结果并进行整合,将查询压力分散到多个节点上,避免了单个节点CPU、磁盘、内存等处理能力不足的情况。另外,当有新节点加入时,会自动将部分分片迁移到新节点上,实现负载均衡。这个功能由ES自动完成。相比MySQL下的一个分库分表,开发者需要引入Mycat等中间件,指定分库分表等繁琐的流程是不是一个巨大的进步?这意味着ES具有非常强大的水平扩展能力。集群可以轻松扩展到数百或数千个节点,轻松支持PB级数据查询。当然,ES的强大还不止于此。还有采用主从分片提升搜索吞吐量,利用节点故障检测,Raftmaster选择机制提升容灾能力等,这些都不是本文的重点,读者可以自行查看。总之,经过上面的简单总结,你只需要明白一件事:ES的分布式架构设计天然就支持海量数据查询。那么ES的索引数据(index)是如何生成的呢?接下来,我们来看看本文的重点。如何构建ES索引要构建ES索引数据,首先要有一个数据源。一般我们会使用MySQL作为数据源。可以直接从MySQL中取数据,然后写入ES。但是这种方式直接调用在线数据库查询可能会影响在线业务。比如考虑这样一个场景:电商APP中最常用的业务场景一定是用户输入关键词查询对应的商品,那么商品会有哪些信息呢?一个商品会有多个sku(sku是同一个商品下不同规格的分类,比如苹果手机有iPhone6、iPhone6s等),会有它的price、title等基本属性,以及商品会有分类(家居、服装等)、品牌、库存等,为了保证表设计的合理性,我们会设计几个表来存储这些属性。假设有product_sku(sku表)、product_property(基本属性表)、sku_stock(存货表)、product_category(品类表)这几个表。那么为了在商品展示列表中展示所有这些信息,就必须将这些表进行join,然后写入到ES中,这样查询的时候就会在ES中获取到所有的商品信息。由于该方案直接在MySQL中执行join操作,当乘积达到千万级时,对在线DB服务的性能影响很大,显然不可行,那么如何生成中间表呢?既然直接在MySQL中操作是行不通的,那能不能把MySQL中的数据同步到别的地方,然后生成中表呢?即增加一个中间层进行处理,避免直接操作在线DB。说到这里相信大家会对计算机界的那句名言有进一步的理解:没有什么是加个中间层解决不了的。如果有,则添加另一层。这个中间层就是Hive。什么是HiveHive是一个基于Hadoop的数据仓库工具,用于数据的抽取、转换和加载。这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。其意义在于将编写好的HiveSQL转化为复杂难写的map-reduce程序(map-reduce是一种专门用于大规模数据集(大于1TB)的并行计算编程模型)。也就是说,如果数据量很大,可以通过将MySQL数据同步到Hive,然后Hive生成上面提到的product_tmp中间表,来大幅提升性能。Hive生成的临时表存储在HBase(分布式、面向列的开源数据库)中。生成后会定时触发dump任务调用索引程序,索引程序主要从HBase中读取全量数据,处理业务数据,Flush到ES索引中。整个过程是这样的:这样建立索引看起来很美,但是需要知道的是Hive先执行join任务是非常耗时的。在我们的生产场景中,由于上千万级的数据,执行join任务通常需要几十分钟的时间,而从执行join任务到最终更新到ES的整个过程往往至少需要半个小时。如果在此期间商品的价格、库存、在线状态(如下架)等重要字段发生变化,索引将无法更新,对用户体验影响很大。在优化之前,我们经常会看到一些ES搜索到的商品是在线的,实际上是下架的,严重影响了用户体验。那么如何解决呢,有一个可行的方案:创建宽表。既然发现hivejoin是性能的主要瓶颈,那我们能不能避免这个过程呢?能不能把product_sku、product_property、sku_stock等表在MySQL中合并成一张大表(我们称之为宽表)。这样一来,每行产品相关的数据都有了,所以在将MySQL同步到Hive之后,Hive就不需要进行耗时的join操作了,整体的处理时间大大提高了。从Hive同步MySQL,然后转储到ES索引,从半个多小时缩短到不到几分钟。看起来确实不错,但是几分钟的索引延迟还是让人无法接受。为什么Hive不能实时导入索引因为Hive是基于静态批处理的Hadoop构建的,Hadoop通常有很高的延迟,在提交和调度作业时需要很大的开销。因此,Hive无法实现对大规模数据集的低延迟快速查询操作,全量千万级数据从索引程序导入到ES集群至少需要几分钟。另外,宽表的引入,其维护也成为了新的问题。想象一下,SKU库存发生了变化,产品被下架,价格也被调整。那么除了修改原表的记录(sku_stock、product_categry等)外,还必须更新原表所有变化记录对应的宽表中的所有记录。这对代码维护来说是一场噩梦,因为你需要在所有产品相关的表发生变化后立即更改宽表的逻辑,这与宽表的变化逻辑是紧耦合的!PB级ES准实时索引构建如何解决?仔细观察上面两个问题,其实是同一个问题。如果我们能实时监控DB的字段变化,然后把变化的内容实时同步到ES和widetable,我们的问题就解决不了了。向上。如何实时监控表字段的变化?答:二进制日志。下面结合Binlog一起回顾一下MySQL的主从同步原理:MySQLmaster将数据变化写入二进制日志(binarylog,这里记录的记录称为binarylogevents,可以通过showbinlogevents查看)。MySQLslave将master的二进制日志事件复制到它的中继日志中。MySQLslave重放中继日志中的事件,将数据变化反映到自己的数据中。可以看出,主从复制原理的关键在于Master和Slave遵循一套协议,实时监控binlog日志,更新从表数据。那我们是不是也可以开发一个遵循这个协议的组件,在组件作为slave的时候,实时获取binlog日志,监控表字段变化呢?阿里的开源项目Canal就是这样做的,它的工作原理是这样的:Canal模拟MySQLslave的一个交互协议,伪装成MySQLslave,向MySQLmaster发送一个dump协议。MySQLmaster收到dump请求并开始将二进制日志推送到slave(即Canal)。Canal解析二进制日志对象(最初是字节流)。这样就可以通过Canal获取binlog日志了。当然Canal只是获取从master接收到的binlog,还需要对binlog进行分析过滤。另外,如果我们只对某些表的字段感兴趣,应该怎么配置,拿到binlog后传给谁呢?这些都需要一个统一的管理组件,阿里的水獭就是这样做的。什么是OtterOtter是阿里提供的基于数据库增量日志解析,准实时同步到本地或远程机房MySQL数据库的分布式数据库同步系统。其整体架构如下:注:以上是我公司在Otter的基础上修改后的业务架构,与原来的Otter略有不同,但大同小异。主要工作流程如下:在Manager中配置ZK、需要监控的表、负责监控表的节点,然后将配置同步到Nodes。节点启动后,其canal会监听binlog,然后经过S(select)、E(extract)、T(transform)、L(load)四个阶段将数据发送给MQ。然后业务可以订阅MQ消息做相关的逻辑处理。画外音:Zookeeper主要协调节点间的工作。比如在跨机房同步数据时,可能需要将数据从A机房的节点同步到B机房的节点,Zookeeper来协调。大家应该注意到了,node中有四个阶段:S、E、T、L,它们的主要作用如下:选择阶段:解决数据源的差异,比如访问canal获取增量数据,或者访问othersystems获取其他数据等Extract阶段:组装数据,对各种数据源进行数据组装和过滤,如mysql、oracle、store、file等Transform阶段:数据抽取和转换过程,将数据转换成需要的类型由目标数据源。Load阶段:数据加载,将数据加载到目标端,比如写入迁移后的数据库,MQ,ES等。以上基于阿里Otter改造的一套数据服务称为DTS(DataTransferService),即是,数据传输服务。搭建完这套服务后,我们可以通过订阅MQ实时写入ES实时更新索引,也可以通过订阅MQ更新宽表的字段。针对上述宽表字段更新与原表紧耦合的问题,基于DTS服务的索引改进架构如下:注:“构建数据”模块对实时索引更新是透明的。该模块主要用于更新或插入MySQL宽表时。因为对于宽表来说,它是几个表数据的并集,所以不会更新哪个字段被监控,而是把所有商品相关的表数据都拉回来更新到宽表中。因此,通过MySQL宽表全量更新+基于DTS的实时索引更新,解决了索引延迟问题,可以实现秒级ES索引更新!以下是您可能比较关心的几个问题。我简单罗列一下:①需要订阅哪些领域?对于MySQL宽表,因为需要保存商品的完整信息,所以需要订阅所有字段,但是对于红框内的实时索引更新,只需要订阅库存、价格等字段即可.因为这些字段如果不及时更新会对销售产生很大的影响,所以我们只需要在实时索引中关注这些敏感字段即可。②索引实时更新,还需要全量更新吗?是的,主要有两个原因:实时更新依赖消息机制,不能100%保证数据完整性,需要全量更新来支持。消息积压等都会有告警,所以我们每天只会进行一次全量索引更新。索引集群异常或崩溃后,可以快速重建索引。③全量指数的更新数据会覆盖实时指数吗?是的,想象这样一个场景,你在某个时刻触发了实时索引,然后此时还在执行全量索引,还没有执行实时索引更新。记录,这样在执行全量索引时,会覆盖之前实时索引中更新的数据。处理这种情况的一种可行方法是,如果正在构建全量索引,可以延迟实时索引更新消息,等全量更新完成后再消费。也是这个原因,我们一般在凌晨执行全量索引。由于是业务淡季,可以最大程度避免此类问题的发生。总结本文简单总结了我公司在PB级数据下构建实时ES索引的一些思路,希望对大家有所帮助。文章只是简单提到了ES、Canal、Otter等阿里中间件的应用,并没有介绍这些中间件的详细配置和原理。这些中间件的设计非常值得我们学习。比如ES在提高搜索效率、优化存储空间等方面做了很多工作。再比如Canal是如何实现高可用的,Otter实现远程跨机房同步的原理等等,建议有兴趣的读者以后可以研究一下,相信你会受益匪浅。作者:孔武编辑:陶佳龙征稿:有意投稿或求报道,请加小编微信gordonlonglong