Elasticsearch的完整读写流程

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: Elasticsearch的完整读写流程

前言

看到标题以后大家有些人可能感觉有点小题大做,毕竟cilent端几行代码就能解决的问题,没必要兴师动众的来仔细讲一下。其实如果你仅仅想使用一下elasticsearch的功能,并不追求性能以及高可用性,那么这么想无可厚非。

但是如果想在生产环境下使用elasticsearch,尤其是高并发高吞吐量的场景下,那么性能优化和高可用性就不可或缺了,要做到上面两点那么数据读写这两个操作的优化是必不可少的。古语有云:“工欲善其事,必先利其器”。想要优化这两个操作,必须先了解这两个操作的原理。

曾经有一个大神说过:“完全理解了数据库的读写流程,这个数据库一半的内容已经掌握了”。既然如此重要且势在必行,那就先在网上学习下,但是网上的博客写得不太详细而且很多地方还有些小问题,于是仔细逛了一遍官网以及看了部分源码后,将相关的内容整理了出来,供自己以及需要的兄弟姐妹们观摩学习。

正文

写数据

先把我画的流程图拿出来,大家可以照着图来理解接下来的流程:

网络异常,图片无法展示
|

image.png

client写入数据时首先先连接到elasticsearch cluster的coordinator node(如果对elasticsearch的node不了解的话,可以参考下这篇文章),coordinator node根据需要写入数据的doc id字段(默认使用该字段,如果数据写入指定了routing的话,则使用routing进行分片路由)进行路由到对应的分片。路由计算方式:shard=hash(document id)%(num_of_primary_shards),然后根据节点的cluster state找到该shard所在的data node,请求就被转发到了该data node

refresh

下面就开始正式地写数据流程了。首先数据被写入到elasticsearch的index buffer中,该buffer在elasticsearch的heap中。写完index buffer后,数据才会写入到translog中,translog写入完毕后即可以返回客户端写入成功。此处有几个问题需要强调下,也是网上的资料经常出问题的地方,划重点

index buffer和translog到底孰先孰后

  • 和数据库不同,数据库是先写CommitLog,然后再写内存,而Elasticsearch是先写内存,最后才写TransLog。这个有悖常理,笔者也曾经很污理解,后来考虑到可能是因为Lucene的内存写入很重很复杂,很容易失败,比如分词,字段类型不匹配且无法转型,字段长度超过限制等,为了避免TransLog中有大量无效记录,减少recover的复杂度并提升效率,所以就把写Lucene放在了写translog前面。
  • 此处需要说明下,网上很多博客都说先写translog后写index buffer或者同时写translog和index buffer的,这些优势有文艺的,为了证明,拿出最好的证据-源码来说明问题:

public IndexResult index(Index index) throws IOException {
       .......
                final IndexResult indexResult;
                if (plan.earlyResultOnPreFlightError.isPresent()) {
                    indexResult = plan.earlyResultOnPreFlightError.get();
                    assert indexResult.getResultType() == Result.Type.FAILURE : indexResult.getResultType();
                } else if (plan.indexIntoLucene || plan.addStaleOpToLucene) {
                 // 将数据写入lucene,最终会调用lucene的文档写入接口
                    indexResult = indexIntoLucene(index, plan);
                } else {
                    indexResult = new IndexResult(
                        plan.versionForIndexing, getPrimaryTerm(), plan.seqNoForIndexing, plan.currentNotFoundOrDeleted);
                }
                if (index.origin().isFromTranslog() == false) {
                    final Translog.Location location;
                    if (indexResult.getResultType() == Result.Type.SUCCESS) {
                        location = translog.add(new Translog.Index(index, indexResult)); 
                    ......
                    // 将数据写入lucene后才开始写translog
                    indexResult.setTranslogLocation(location);
                }
              .......
        }

多副本时如何写入

  • 上面的是单副本情况下的写入,如果是多副本写入可以参考下面的图。默认情况下,当primary shard写入成功后,即返回写入成功,后续replica shard通过异步的方式同步数据或者translog恢复数据。
  • 该机制可以通过设置index.write.wait_for_active_shards参数进行设置。该参数可以使用all或者在1到副本数加1(number_of_replicas+1)之间任何的一个整数值。如果是all也就是等待主分片和副本都写入成功,请求才返回,这样可以保证elasticsearch的强一致性,但是代价一是写入线程会阻塞,影响吞吐量;二是会影响elasticsearch的可用性。有一个节点不正常写入就会阻塞,直到节点恢复或者写入超时。

网络异常,图片无法展示
|

image.png

index buffer refresh

  • 数据写入到这里的时候还无法被搜索到,默认1s后执行refresh操作,将内容写入文件系统缓冲区(os cache)中的新段(segment)。此segment的内容尚未被fsynced(未被写入到硬盘),但是segment是打开的,内容可被搜索。
  • 但是1s一次的refresh会导致频繁生成新文件,在占用大量句柄以及系统资源的同时也会影响到查询数据的效率。所以对于实时性要求不是很高的业务场景,可以将refresh的时间拉长,比如30s,"index.refresh_interval":"30s"
  • 最后在refresh写完segment后会更新shard的commit point。commit point在shard中以segments_xxx名字的文件存在。用来记录每个shard中segment相关的信息。

flush

  • 当os cache中的segments数据积累到一定时间(默认30分钟)或者translog达到一定大小时(默认512M),os cache中的segments会被flush到硬盘上进行持久化,持久化成功后,对应的translog由于失去存在的意义而被删除。
  • 此处有几个参数和flush相关,大家可以根据需求进行配置: index.translog.flush_threshold_ops:当发生多少次操作时进行一次flush。默认是 unlimited。 index.translog.flush_threshold_size:当translog的大小达到此值时会进行一次flush操作。默认是512mb。 index.translog.flush_threshold_period:在指定的时间间隔内如果没有进行flush操作,会进行一次强制flush操作。默认是30m。 index.translog.interval:多少时间间隔内会检查一次translog,来进行一次flush操作。es会随机地在这个值到这个值的2倍大小之间进行一次操作,默认是5s。

segment merge

  • segments被持久化后写入硬盘,硬盘上的segments的数量就会越来越多,又会引发句柄占用以及影响查询效率,所以此时会触发segment merge操作。由于这个操作比较复杂,受限于本文长度就先不在这里说了,后续会专门写一篇文章来说明。

Delete&&Update操作

Delete&&Update操作也是一种特殊的写操作,但是由于Delete&&Update操作并不是即时生效,而是通过标记删除的方式来实现,最终通过segment merge操作实现真删。所以和标准的写入还是有一定的差别,下面来说一下具体差别:

  • Delete:磁盘上的每个分段(segement)都有一个.del文件与它关联。当客户端发送删除请求时,该文档未被真正删除,而是在.del文件中标记为已删除。此文档仍然可能被搜到,但会从结果中过滤掉。当分段合并时,在.del文件中标记为删除的文档不会包括在新的合并段中。
  • Update:创建新文件,Elasticsearch将该文档分配一个版本号。对文档的每次更改都会产生一个新的版本号,版本号使用versionMap来进行管理,用以减少热点数据的多次磁盘IO开销。当执行更新时,旧版本在.del文件中标记为已删除,并且并且在新版本的分片中编入索引。旧版本可能仍然与搜索查询匹配,但是从结果中将其过滤掉。

读数据

elasticsearch中的读操作包含get操作以及search操作,下面就根据这两个操作来详细讲解elasticsearch是如何进行读数据操作的。为了节省篇幅,此处统一说明一下,client从coordinator节点路由到对应数据所在节点的过程与写入数据的流程相同,请大家参考上面写数据相关的内容。下面直接就从数据节点开始讲解。

名词解释

  • 正排索引:doc id -> value,方向是正常方向,所以称为正排索引。使用场景是get这种通过doc id找value的场景。在shard中以后缀名为fdx与fdt结尾的文件存储,dfx是索引文件,fdt是数据文件。这两部分数据是硬盘消耗的大户,elasticsearch中的source字段即存储在fdt文件中。
  • 倒排索引:index -> doc id,方向与上面的相反,所以称为倒排索引。使用场景是search通过查询条件匹配对应的倒排索引拿到对应数据的doc id,拿到doc id后查询正排索引拿到真正的数据。在shard中以后缀名除了正排索引外绝大部分都是各种类型的倒排索引,每一种倒排索引也分为索引文件和数据文件。这两部分数据是内存消耗的大户,elasticsearch中的倒排索引都会加载到off heap中用来加速查询,这也是要留给lucene一半内存最主要的原因。

get

get操作即使用doc id字段进行单条数据的查询,查询流程图如下:

网络异常,图片无法展示
|

image.png

  • 首先使用doc id字段从os cache中的translog中查询,如果能查询到就直接返回客户端;
  • 如果os cache中的translog没有查询到的话,再去disk上的tranlog中查询,如果能查询到就直接返回客户端;
  • 如果reanslog中没有查询到对应的数据,再去segment中查询对应的数据。首先把正排索引的fdx数据加载到off heap中去查询doc id。如果查询不到则直接返回null;如果能查询到doc id,根据查询结果中的偏移量直接去硬盘上查询对应的原始数据并返回。
  • 此处大家思考下get操作(其实update以及delete操作也会优先查询translog)为什么要优先查询translog后再去查询segment数据?这里说明一下原因:上文已经说过了delete以及update操作在elasticsearch中都是先打删除标记然后通过segment merge操作进行真删,所以一条数据可能在elasticsearch中有几个版本,而最新的版本可能会存在于translog中。所以如果在translog中查询到目标数据直接返回即可,一定是最新的数据;如果translog中没有目标数据,再去segments中查询。

search

search也是elasticsearch中比较复杂的流程。总体分为term search以及分词search。分词search内容较多,考虑到篇幅,后续会单独写一篇文章讲述。此处仅仅是为了讲解search的原理和流程,所以使用term search来进行讲解。

search操作的阶段取决于search type的选择,后续单独写一篇文章来说明elasticsearch的search type。而search type默认使用query then fetch类型,该类型由两个阶段组成:查询阶段(query)和获取阶段(fetch)阶段。

  • 查询阶段:在此阶段,协调节点将搜索请求路由到索引中的所有分片(包括:主分片和副本分片)。分片独立执行搜索,并根据相关性分数创建一个优先级排序结果.所有分片将匹配到的文档和相关性分数的文档id返回给协调节点。协调节点创建一个新的优先级队列,并对全局结果进行排序。可以有很多文档匹配结果,但默认情况下,每个分片将前10个结果发送到协调节点,协调节点创建优先级队列,从所有分片中分选结果并返回前10个匹配结果。
  • 获取阶段:在协调节点对所有的结果进行排序,生成全局排序的文档列表后,它将所有分片请求原始文档。所有的分片都会丰富文档并将其返回到协调节点。

term search的流程如下:


网络异常,图片无法展示
|

image.png

  • 首先client会根据search中的term去elasticsearch heap中的Segment Cache中查询FST数据(如果不知道FST数据建议参考之前的博文),简单来说就是倒排索引的前缀树。
  • 根据FST数据来查询对应的倒排索引tip文件,将tip文件加载到off heap中进行查询,将查询后的索引数据查询倒排索引数据tim文件,得到对应的doc id列表。
  • 根据doc id列表将对应的正排索引文件fdx数据当如到off heap中查询对应doc id的地址,查询之后去fdt文件中取出对应的原始数据中必要的信息返回到协调节点(完整的数据在fetch阶段获取)。




相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
2天前
|
存储 缓存 负载均衡
elasticsearch写入流程和请求检索流程原理全方位解析
elasticsearch写入流程和请求检索流程原理全方位解析
|
10月前
|
存储 缓存 自然语言处理
Elasticsearch分布式模式下读写流程 1
Elasticsearch分布式模式下读写流程
50 0
|
10月前
|
存储 缓存 Java
Elasticsearch分布式模式下读写流程 2
Elasticsearch分布式模式下读写流程
41 0
|
11月前
|
存储 机器学习/深度学习 SQL
阿里云 Elasticsearch简介和购买流程
开源Elasticsearch是一个基于Lucene的实时分布式的搜索与分析引擎,是遵从Apache开源条款的一款开源产品,是当前主流的企业级搜索引擎。作为一款基于RESTful API的分布式服务,Elasticsearch可以快速地、近乎于准实时地存储、查询和分析超大数据集,通常被用来作为构建复杂查询特性和需求强大应用的基础引擎或技术。
|
存储 监控 负载均衡
大数据数据存储的搜索引擎Elasticsearch的调优的磁盘读写优化
Elasticsearch是一个可扩展的搜索引擎,可以在同一个集群中部署多个Elasticsearch节点,以提高性能和可用性。
71 0
ElasticSearch 简单查询流程
GET请求的基本流程
91 0
ElasticSearch 简单查询流程
ElasticSearch 简单写入流程
该流程不涉及到lucene底层,lucene底层写入会另外再开一篇文章做详细描述
80 0
|
存储 负载均衡 监控
Elasticsearch文档读写模型实现原理
Elasticsearch文档读写模型实现原理
Elasticsearch文档读写模型实现原理
|
存储 缓存 负载均衡
分布式搜索引擎ElasticSearch读写数据工作流程
分布式搜索引擎ElasticSearch读写数据工作流程
359 0
分布式搜索引擎ElasticSearch读写数据工作流程
|
15天前
Elasticsearch安装配置文件
Elasticsearch安装配置文件
15 0