开头
Elasticsearch是一个被广泛使用的搜索引擎,通过本文你可以了解:
1.Elasticsearch适合做什么,不适合做什么,对于你判断是否需要使用Elasticseaerch给出一个参考。
2.Elasticsearch与MySQL在索引查询上的原理分别是什么,告诉你Elasticsearch为什么查询比MySQL快。
3.如果你要使用Elasticsearch,应该如何做架构设计,Elasticsearch与MySQL如何相互配合。
4.如果你的搜索很慢,应该如何排查Elasticsearch的性能问题。
你不能了解:
1.如何安装、配置、部署Elasticsearch。
2.如何使用Elasticsearch。
Elastichsearch的数据存储特性
ES是一个分布式的搜索引擎。但是,我认为ES首先是一个数据库,它具有持久化数据的能力。ES在数据存储上有2个重要特点:分片和副本。分片是指将一份完整的数据集(索引)分成若干片段的数据;副本是分片数据的备份。分片使es保存的数据具有很强的横向扩展能力,可以支持海量数据的高性能存储和查询。副本使数据具有高可用和容灾能力,即使部分ES数据节点挂掉仍然可以正常运行。
补充一些注意点吧:
1.每块分片都是一个独立完整的搜索引擎,只是数据是完整数据集的子集。
2.你可以设置副本的份数,也可以设置为0,即没有副本。副本数越高可用性越高(可以让更多的数据节点挂掉),并且副本分片可以服务于读请求,增加副本数还可以提升查询性能,代价是降低了写入性能且需要更多的存储空间。
3.如果持有主分片的节点挂掉了,一个副本分片就会晋升为主分片的角色。在索引写入时,副本分片做着与主分片相同的工作。新文档首先被索引进主分片然后再同步到其它所有的副本分片。
4.同一个分片和它对应的副本应该分散在不同的节点上,否则起不到提升可用性的作用。你看下图,有一个分片数为3,副本数为1的索引分布在2个数据节点上,无论哪个节点挂掉,剩下的节点上总能保持完整的数据信息。当然,ES会根据你设置的分片和副本数,以及集群中的数据节点数自动分配,作为使用者无须额外操作,但你应该知道这个原理。
你可能已经想到一种特殊情况了:单机部署。很明显,副本对单机部署模式毫无意义,所以如果你需要单机部署的话,请把索引的副本数设置为0,以节省空间和提升写入性能。
ES不适合做什么
以上,你会感觉ES既能满足高性能读写,又能保证高可用,再加上ES最擅长的各种花式查询搜索,作为数据库好像还不错。那么,ES到底是否适合作为产品的数据库选型呢?我个人认为,大多数业务场景下是不适合的。原因如下:
1.ES不支持事务。没有传统关系型数据库的事务和锁,难以应付数据一致性要求高的场景,比如订票系统,商品秒杀系统,银行交易系统。
2.ES的查询是近实时的,而非实时的。这意味着,当你写入一条数据到ES中,需要等待一段时间(默认是1秒)才能被查询到。换句话说,如果你刚提交了数据,ES也告诉你提交成功了,但在提交后的1秒内ES突然停机,那你提交的数据仍然是有可能丢失的(可能你会觉得这很严重,如果是单机模式确实如此,但别忘了ES是支持集群部署的,集群专治宕机停电)。这是因为ES为了提升搜索性能做出的牺牲。持久化一条数据需要对磁盘进行io操作以真正的避免数据丢失,但是磁盘io操作比较耗时,所以它不能在每索引一条数据后就执行一次磁盘写操作。为了避免数据提交对磁盘的高频写操作,从数据提交到持久化之间,还有一层称为FileSystem Cache的系统缓存。ES会定期对FileSystem Cache中的数据刷新到磁盘,以达到批量写入的效果,从而减少对磁盘的io操作。这个刷新周期可以手动设置,最快可以设置为1毫秒,但从本质上讲仍然属于定期刷新。前面我提到,通过集群部署可以避免突然停机带来的数据丢失问题,那你可能会有疑问,没有分布式特性的mysql是如何避免数据更新丢失的?由于本篇主要围绕ES,不再继续展开讨论,感兴趣的同学可以自行搜索:"mysql两阶段提交","mysql redo log","mysql binlog"等内容。
3.数据更新效率低。ES本质上没有更新操作,所谓的ES更新实际上是删除+新增两步操作,高频率的数据增删很容易触发段(segment)文件合并。段合并是对文件的重新组装,相对内存操作而言更耗时。因此如果你需要高频的更新数据,比如你需要设计一个计数器,也许redis会更适合。
4.安全性不足。开源版本的ES本身不带权限认证模块,商业版的xpark插件支持用户身份认证。但不管怎样,和mysql细致且健全的权限认证体系相比,ES的数据让人总感觉是在裸奔。顺便介绍一点提升ES安全性的办法:首先是配置ES节点为内网ip,避免外部请求直接访问;其次要设置防火墙,限制指定ip可以访问es节点;如果需要通过kibana等工具访问数据,可以通过反向代理设置访问密码,并结合vpn使用。
通过以上分析,你可以判断出自己的系统是否适合用ES作为数据库选型。ES并非不能当作数据库来使用,只要你能充分发挥它的优点,避免它的不足。但不管怎样,以上问题都是ES不擅长的领域。
Elasticsearch适合做什么
ES就像它的名字,最适合它的工作当然是搜索,这得益于ES的全文检索能力。关于搜索,我补充一点,ES除了可以实现文档关键字匹配度搜索以外,也支持按一定业务逻辑干涉的"智能搜索"。例如你在电商网站搜索"苹果"的时候,结果是优先展示手机还是水果;或者你在外卖APP上搜索"烧烤",首页展示的门店总是在配送范围之内而且评论口碑都不错。这些场景都可以用ES通过全文检索结合推荐算法来实现。除了全文检索以外,对于各种条件查询,数据计算、聚合等查询类的任务,ES同样是非常适合的。例如,假设你要实现一个千万级用户的附近的人功能,需要在千万用户中找出离你最近的200个人,并加上一些社交属性的可选条件,比如年龄范围,性别,最近上线时间等,甚至排序也是一个复杂的计算规则。如果你通过mysql这样的传统数据库来查询,那么在多表连接,条件过滤,复杂排序这几方面都存在巨大的挑战。然而,ES却非常擅长做这样的事。我给你一个建议就是,当你觉得一个查询接口用mysql实现效率太低了的时候,除了死磕SQL优化以外,还可以了解一下ES。
总结一下,ES适合做全文检索、数据统计与聚合、海量数据的复杂查询。当下一种流行的用法是,传统数据库+ES配合,核心业务数据流转在传统数据库,ES的数据来源于传统数据库的数据同步。传统数据库负责主要业务的CRUD,ES负责对数据精确度不高的复杂条件查询及搜索。
Elasticsearch与MySQL查询原理分析
MySQL的索引原理
在介绍elasticsearch的索引原理之前,我们简单了解一下mysql的索引知识。
假如我们有如下数据:
在mysql的InnoDB引擎中,主键默认建立聚集索引(索引里包含row的全部信息),数据结构为B+树。
B+树的具体定义本文不介绍,感兴趣的同学点此了解,但有几个特点我提一下:
1.B+树是一种多路查找树,其每一个节点的孩子数可以多于两个,且每一个节点处可以存储多个元素。
2.每个节点到叶子节点的高度都是相同的,这样可以保证B树的查询是稳定的。
3.每一个节点存储的元素是经过排序的,节点的子树满足:左子树元素≤父节点元素;右子树元素>父节点元素
4.非叶子节点只保存key,叶子节点保存key和data。
为了方便对年龄进行查询,我们需要对年龄字段单独建立索引,数据结构仍然为B+树:
开发者自己建立的索引叫做二级索引,二级索引里存储的内容是主键。这就意味着,如果你想查询满足age=18这个条件的完整信息,数据库查询会经历两个过程:
1.InnoDB首先搜索age key索引,找出age=18的id;
2.搜索主键索引,找出对应id的完整信息。
通过查询条件找到主键,再通过主键查询完整信息的过程,叫做回表。很明显,回表增加了查询次数,降低了查询效率,你可以自己了解一下如何避免回表。
我再留一个问题给你思考一下:平衡二叉树是二分查找效率最高的数据结构,为什么MySQL不使用平衡二叉树,而要使用B+树这种多叉树来存储索引呢?
倒排索引
倒排索引是elasticseach实现快速搜索的核心。在elasticsearch中,记录(es中称为"文档")的每个字段都会建立索引,无需手动配置。上一节中的原始数据,如果用elasticsearch保存,索引结构如下:
17,18,20这些叫做term,而[100,300,500]叫做posting list。Posting list就是一个int的数组,存储了所有符合某个term的文档id。
Term Dictionary
为了能快速找到某个term,将所有的term排个序,二分法查找term,logN的查找效率,就像通过字典查找一样,这就是Term Dictionary。现在再看起来,似乎和传统数据库的方式类似啊,为什么说查询更快呢?
Term Index
假设我们有很多个term,比如:
Carla,Sara,Elin,Ada,Patty,Kate,Selena
如果按照这样的顺序排列,找出某个特定的term一定很慢,因为term没有排序,需要全部过滤一遍才能找出特定的term。排序之后就变成了:
Ada,Carla,Elin,Kate,Patty,Sara,Selena
这样我们可以用二分查找的方式,比全遍历更快地找出目标的term。这个就是 term dictionary。有了term dictionary之后,可以用 logN 次磁盘查找得到目标。但是磁盘的随机读操作仍然是非常昂贵的(一次random access大概需要10ms的时间)。所以尽量少的读磁盘,有必要把一些数据缓存到内存里。但是整个term dictionary本身又太大了,无法完整地放到内存里。于是就有了term index。term index有点像一本字典的大的章节表。比如:
A开头的term ……………. Xxx页
C开头的term ……………. Xxx页
E开头的term ……………. Xxx页
如果所有的term都是英文字符的话,可能这个term index就真的是26个英文字符表构成的了。但是实际的情况是,term未必都是英文字符,term可以是任意的byte数组。而且26个英文字符也未必是每一个字符都有均等的term,比如x字符开头的term可能一个都没有,而s开头的term又特别多。实际的term index是一棵树:
这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。
所以term index不需要存下所有的term,而仅仅是他们的一些前缀与Term Dictionary的block之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使term index缓存到内存中。
现在我们可以回答为什么Elasticsearch检索可以比MySQL快了。Mysql只有term dictionary这一层,是以B+树的方式存储在磁盘上的。检索一个term需要若干次的random access的磁盘操作。而Elasticsearch在term dictionary的基础上添加了term index来加速检索,term index以树的形式缓存在内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘的random access次数。
额外值得一提的两点是:term index在内存中是以FST(finite state transducers)的形式保存的,其特点是非常节省内存。Term dictionary在磁盘上是以分block的方式保存的,一个block内部利用公共前缀压缩,比如都是Ab开头的单词就可以把Ab省去。这样term dictionary可以更节约磁盘空间。
总结
Elasticsearch就是尽量将磁盘里的东西搬进内存,减少磁盘随机读取次数(同时也利用磁盘顺序读特性),结合各种压缩算法,高效使用内存,从而达到快速搜索的特性。
使用Elasticsearch的基本架构设计
Elasticsearch一般不作为主数据源,因此在系统中应该包含一个主库和Elasticsearch。主库负责主要业务的数据流转,Elasticsearch负责对数据精确度不高的复杂条件查询及搜索。下图是一个普通的系统架构模型。
如图上蓝色线所示,表示同步写过程,同步写结束代表主要业务完成。
然后,如图中绿色线所示,有一个异步写服务,监听 MQ 的消息,继续完成辅助数据的更新操作。注意点:
MQ 消息不一定包含完整的数据,甚至可能只包含一个最新数据的主键 ID,我们需要根据 ID 从查询服务查询到完整的数据。
ES 不适合在各索引之间做连接(Join)操作,适合保存扁平化的数据。比如,我们可以把订单下的用户、商户、商品列表等信息,作为内嵌对象嵌入整个订单 JSON,然后把整个扁平化的 JSON 直接存入 ES。
对于查询服务,如图中红色线所示,我们需要根据一定的上下文条件(比如查询一致性要求、时效性要求、搜索的条件、需要返回的数据字段、搜索时间区间等)来把请求路由到合适的数据库,并且做一些聚合处理:需要根据主键查询单条数据,可以从 MySQL Sharding 集群或 Redis 查询,如果对实时性要求不高也可以从 ES 查询。按照多个条件搜索订单的场景,可以从 MySQL 索引表查询出主键列表,然后再根据主键从 MySQL Sharding 集群或 Redis 获取数据详情。各种后台系统需要使用比较复杂的搜索条件,甚至全文搜索来查询订单数据,或是定时分析任务需要一次查询大量数据,这些场景对数据实时性要求都不高,可以到 ES 进行搜索。此外,MySQL 中的数据可以归档,我们可以在 ES 中保留更久的数据,而且查询历史数据一般并发不会很大,可以统一路由到 ES 查询。
Elasticsearch与MySQL的数据同步方案
Elasticsearch不适合作为主数据源,而更适合作为数据库的"备份",因此如果你已经确定要使用Elasticseach,你要面对的第一个问题是,如何维护Elasticsearch中的数据。总的来讲,Elasticsearch的数据维护包括:数据全量同步,数据增量同步,数据正确性校验。
数据全量同步
一般来讲,数据全量同步只触发于初始化阶段,目的是将数据库中的历史数据同步到Elasticsearch中。对于全量同步,最简单的做法是写一个数据迁移脚本,如果嫌麻烦,也可以使用datax这类数据同步工具,总体来讲,实现难度都不大。
数据增量同步
数据增量同步是在程序长期运行中,业务数据更新后自动同步到elasticsearch,我总结了4种Elasticsearch与MySQL的数据增量同步策略:
1.代码级同步:利用代码的顺序执行,在MySQL的增、删、改操作执行后,立即操作Elasticsearch进行增、删、改操作。这种方案的优点是实现简单,缺点也很明显:1.容易写漏而引发bug。2.每处操作都要写两遍数据操作的代码,增加了代码复杂度,后期的可维护性不高。3.如果是同步写,不仅会增加代码的执行时间,还会增加因同步Elasticsearch失败而导致的主流程的失败率。总的来讲,这种方案过于简单粗暴,一般场景不推荐使用。
2.利用消息队列解耦:你需要一个专门用于操作Elasticsearch数据的服务。当代码执行MySQL的增、删、改操作执行后,通过消息队列将改变的数据发送到Elasticsearch数据服务,由该服务对Elasticsearch进行增、删、改操作。这种方案的优点是业务流程代码只需要发送消息队列,无需和Elasticsearch产生直接关系,Elasticsearch的同步操作也不会影响主流程。缺点是你需要单独维护一个Elasticsearch数据服务。
3.利用logstash-jdbc-input同步:logstash-jdbc-input同步的原理是,logstash-jdbc-input会定时(最小频率是每分钟)向MySQL发出指定的查询语句,并将查询结果按照你的配置写入到elasticsearch。此方案和方案2相比,实现了彻底的业务解耦(方案2还是需要写发消息的逻辑),无需另写一个Elasticsearch数据服务;不足之处是只能定时同步,无法做到近实时同步,适用于对查询即时性要求不高的场景。
4.利用canal同步:canal的同步原理是,1.canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议;2.mysql master收到dump请求,开始推送binary log给slave(也就是canal);3.canal解析binary log对象,还原数据并写入elasticsearch。此方案和方案3相比,二者都做到了很好的解耦,canal同步是近实时的,即时性比logstash-jdbc-input强,但canal只针对mysql同步,logstash-jdbc-input适用于泛SQL系列。
总结一下,如果你的数据源是MySQL,那么方案4是我认为最佳的选择;如果你的数据源是Oracle,SQL Server建议用方案2或方案3。
数据正确性校验
正确性校验的目的主要是对增量同步失败后进行补救,可以提升数据的准确性,一般来说校验是用定时器触发的。常见的校验方案有:
1.逐条校验。逐条校验就是将MySQL中查询的数据和elasticsearch中的数据一一对比,如果发现elasticsearch中的数据和数据库不一样,就触发这条数据的同步。逐条校验的好处是实现简单,缺点是数据量大的情况下校验效率低。
2.分页批量校验。将MySQL中查询的数据和elasticsearch中的数据按分页大小批量比较。和逐条校验的区别是,MySQL查询和和elasticsearch查询都进行了批量读,加快了校验效率。
3.计算摘要。.将MySQL和elasticsearch中相同条件查询的数据进行md5或hash加密,如果两者计算出的摘要信息一致则表示数据正确;如果摘要不一致,就需要进行数据更新。此方案的优点是利用摘要算法避免了大量的比对操作,是三种方案里效率最高的。
Elasticsearch搜索性能问题排查思路
1.用kibana操作elasticsearch。kibana就是开发人员观察elasticsearch的眼睛,kibana的dev tool是操作elasticsearch最方便的工具没有之一,你应该用kibana来操作和调试elasticsearch,如同你用navicat而非命令行来操作MySQL一样。需要小心的是,kibana和elasticsearch都是不需要密码就能使用的,所以为了数据安全,你应该仅开放指定ip来访问kibana,再配合vpn和反向代理的密码访问。
2.开启elasticsearch的慢日志。elasticsearch的慢日志包括了慢索引日志和慢搜索日志。你可以自定义慢索引和慢搜索的阀值,当写索引或搜索的时间超过设置阀值后elasticsearch就会将本次索引或搜索的语句记录到对应的日志里,以便后续分析问题。
3.利用性能监控工具查看elasticsearch的实时运行情况。Elasticsearch本身提供了一组性能监控的接口,理论上通过接口就可以获取elasticsearch的运行状态,如心跳检测,集群健康情况等。但是最方便的办法是使用性能监控工具,其实很多ES的性能监控工具本质上也是调用的elasticsearch接口来获取状态。我推荐一个免费的工具:metricbeat。它可以和kibana集成,实时展示elasticsearch的各项性能指标和资源占用情况。
4.使用profileApi。通过这个功能,可以看到一个搜索聚合请求,是如何拆分成底层的 Lucene 请求,并且显示每部分的耗时情况,类似于MySQL的explain。profileApi结合慢搜索日志,可以很快定位到搜索慢的最终原因。
总结来说,elasticsearch排查问题,要依靠日志分析,profileApi分析,并结合有效的性能监控工具。
总结
本文介绍了ES的存储特性,包括分片和副本。
ES虽然不适合作为主数据库,但非常适合海量数据的复杂条件查询。
MySQL的索引原理是利用B+树作为检索的数据结构,擅长查询最左前缀匹配的条件和范围查询;ES的索引原理是倒排索引,通过Term Index和压缩算法将索引的前缀放入内存从而减少磁盘查询。
使用ES作为数据存储系统,要重点考虑数据与主库同步和数据正确性校验的方案。比较推荐的同步方案是利用消息队列同步,或者使用logstash-jdbc-input或者使用canal;比较推荐的数据正确性校验方案是hash校验。
出现ES查询性能低时,首先要开启慢搜索和慢索引日志,结合性能监控工具查看各项性能指标并配合kibana及profile API辅助调试。