图片来自宝途网【原创稿件】Elasticsearch以分布式存储和分布式搜索着称,那么数据是如何写入Elasticsearch提供优质搜索服务的呢?这篇文章是关于Elasticsearch写索引文件的过程,介绍了Elasticsearch集群架构的特点,如何通过路由将索引文件写到ES,写的文件如何合并刷新供用户搜索,Elasticsearch如何保证写数据存储可靠。我将从以下三个方面来分享:Elasticsearch集群与路由Elasticsearch文件合并与刷新Elasticsearch数据存储可靠性Elasticsearch集群与路由在介绍数据如何写入ES(以下简称ES,统称Elasticsearch)之前,你需要了解ES集群架构,也就是说,了解ES集群节点中数据是如何存储的。这里需要介绍几个关键词,帮助大家理解ES中的一些基本概念:节点(Node):用于运行的ES实例以进程的形式存在,节点运行在物理服务器上。索引文件(Index):是需要写入ES节点的数据,它的数据模型信息Mapping(数据结构)和数据文件(数据内容)。索引文件可以分布在一个节点上,也可以分布在多个不同的节点上。Shard:用于存放索引文件或索引文件的一部分信息。如果一个索引包含大量文档,而单个节点的物理服务器硬件容量有限,那么存储索引文件的容量就会受到限制。这个“大”的索引文件不能存储在一个节点中,所以ES提供了一种分片机制,可以让一个索引文件存储在不同节点的不同分片中。索引文件可以按照一定的维度分成多个部分,每个部分是一个切片,切片由一个节点(Node)管理。一个节点(Node)会管理多个分片,分片可能属于同一个索引,也可能属于不同的索引。为了可靠性和可用性,同一个索引的分片会尽可能分布在不同的节点(Nodes)上。分片类型:分片是承载索引(数据)的基本单位,分为主分片和副本分片两种。primaryshard会尽可能均匀的分布在不同的节点上,replicashard顾名思义就是primaryshard的副本,用来提供数据的冗余副本。一般来说,副本分片和主分片不会出现在同一个节点上,因为当单个节点出现故障时,只有本节点上的分片受到影响,而其他节点上的分片仍然可以正常工作。需要注意的是,只有主分片可以处理索引写入请求,副本分片仅用于存储数据。Replica:同一个shard的备份数据。一个分片可能有0个或多个副本。这些副本中的数据保证是强一致或最终一致的。介绍完ES的基本概念后,我们对ES的数据存储有了一个大概的了解。索引会按照规则进行分片,分片分为主分片和副本分片。主分片用于处理索引写入请求,副本分片用于存储索引数据。分片运行在节点上,节点运行在物理服务器上。由于ES的分布式数据存储方式,索引数据会存储在不同的节点和分片上,所以在编写索引的时候需要遵循一定的规则,也就是接下来要介绍的路由规则。ES的路由规则是在多分片索引中写入数据时,通过路由决定写入哪个分片。判断过程会通过以下公式来实现:shard=hash(routing)%number_of_primary_shardsrouting是一个变量值,默认为文档的_id,也可以设置为自定义值。路由通过hash函数生成一个数,然后用这个数除以number_of_primary_shards(primaryshards的个数)得到余数。0和number_of_primary_shards之间的余数是查找文档所在的分片位置。这就解释了为什么primaryshards的数量应该在索引创建的时候固定下来,永远不会改变:因为如果这个数量改变了,之前所有的路由值都会失效,再也找不到文档了。索引中的每个文档都属于一个单独的主分片,因此主分片的数量决定了索引最多可以存储多少数据(实际数量取决于数据、硬件和应用场景)。下面是一个例子来帮助你理解上面的路由公式。ES集群中的每个节点通过路由知道文档在集群中的存储位置,因此每个节点都有处理读写请求的能力。这种节点称为协调节点。协调节点会根据路由公式计算需要写入哪个分片,然后将请求转发给该分片的主分片节点。需要注意的是,虽然协调节点会根据路由规则处理写请求并转发给主分片。如图1所示,共有三个节点ES1、2、3,每个节点包含若干分片,其中S0、1、2、3为主分片,其他以“R”开头的为副本分片。图1:ES写数据的路由流程这里的路由规则是shard=hash(routing)%4=0,其中4是primaryshard的个数,假设结果为“0”,即写入数据到主分片上的S0。路由过程大致如下:客户端向ES1节点(协调节点)发送写请求,通过路由计算公式得到的值为0,则当前数据应该写入主分片S0。ES1节点将请求转发给S0主分片所在节点ES3,ES3接受请求并写入磁盘。将数据并发复制到两个副本分片R0,其中数据冲突由乐观并发控制。一旦所有的副本分片都报告成功,节点ES3就会向协调节点报告成功,协调节点再向客户端报告成功。上述写入过程涉及协调节点、主分片和副本分片的数据写入。下面我们就来进一步分析这三个block的写法。协调节点ES中接收和转发请求的节点称为协调节点,ES中的所有节点都可以接受和转发请求。当一个节点收到一个写入或更新请求时,它会执行以下操作:①摄取管道:它是一个请求预处理管道,它会根据规则对请求进行预处理。它会检查请求是否符合一个ingestpipeline的pattern,如果符合则执行pipeline中的逻辑,一般对文档进行各种预处理,比如格式调整,添加字段等。②自动创建索引:判断索引是否存在,如果开启自动创建则自动创建,否则报错。③设置路由:获取请求URL或映射中的_routing,如果没有则使用_id,如果不指定_id,ES会自动生成一个全局唯一的ID。_routing字段用于确定文档在索引中分配到哪个分片。④构造BulkShardRequest:创建多操作请求,假设BulkRequest包含多个(Index/Update/Delete)请求,这些请求需要在不同的shard上执行。需要这一步来区分requestsbyshard,将request聚合到同一个shard上构建BulkShardRequest。⑤向主分片发送请求:如果用户请求是写操作,则将请求路由到主分片所在节点,等待主分片写结果的返回信息。primaryshardprimaryshard请求的入口点是PrimaryOperationTransportHandler的MessageReceived。当收到一个请求时,会进行以下步骤:①判断操作类型:如果是BulkRequest,会遍历请求中的子操作,根据不同的操作类型跳转到不同的处理逻辑。②操作转换:将Update操作转换为Index和Delete操作。③解析文档:解析文档的各个字段。④UpdateMapping:如果请求中有新字段,则根据动态映射或动态模板生成对应的映射。如果映射中有动态映射相关设置,则根据设置进行处理。⑤获取序列Id和Version:从SequenceNumberService中获取一个SequenceID和Version。SequenceID用于初始化LocalCheckPoint,version以当前versoin+1为准,防止并发写入造成数据不一致。⑥写入Lucene:锁定索引文档的uid,然后判断uid对应的版本v2是否与之前update转换时的版本v1一致。如果不一致,则返回第二步重新执行。在版本相同的情况下,根据id进行添加或更新操作。如果已经存在相同id的文档,则调用updateDocument接口。⑦写入translog:写入Lucene的Segment后,Translog会以key-value的形式写入,Key为Id,Value为索引文档的内容。查询时,如果请求的是GetDocById,可以直接根据_id从translog中获取。写入translog的操作将在后面的章节中详细说明。⑧批量请求重构:将多个操作中的更新操作转化为索引和删除操作,最终批量请求由索引或删除操作组成。⑨放置Translog:默认情况下,translog会放置在这里。如果对可靠性要求不高,可以设置translog异步放置。同时,存在数据丢失的风险。⑩向副本分片发送请求:将构造好的批量请求发送到各个副本分片,等待副本分片返回,再响应协调节点。如果某个分片执行失败,主分片会向主节点发送请求移除该分片。?等待副本响应:当所有副本分片都返回请求后,更新主分片的LocalCheckPoint。副本分片请求的入口是ReplicaOperationTransportHandler的messageReceived。大体流程与主分片类似,相同步骤不再赘述。列出来供大家参考:判断操作类型:写入请求已经转换为对主分片的增删操作,只需要根据操作类型执行即可。解析文档(ParseDoc):与主分片相同。更新映射:与主分片相同。获取sequenceId和Version:使用主分片发送的内容即可。写入Lucene:与主分片相同。写入Translog:与主分片相同。放置translog:与主分片相同。Elasticsearch文件合并刷新前面介绍了ES索引写入的过程。当索引保存在ES集群中时,会通过路由规则找到对应的分片写入数据。primaryshard收到数据后,也会同步到copy上,整个写操作都在shard上完成。了解了文件的写入过程,我们再仔细看看写入的细节,看看索引最终是如何通过内存写入磁盘的。如图2所示,这里列出了索引写入的步骤,分别在内存和磁盘中完成写入操作。图2:ES写流程如图2所示,写操作的每一步拆解如下:①写请求会把索引(Index)存放在内存区域,称为IndexBuffer。此时的索引文件暂时无法被ES搜索到。②ES默认每秒执行一次Refresh操作,将IndexBuffer中的索引写入Filesystem,也是一块内存区域。将IndexBuffer中的索引转为Segment,此时的数据可以被ES搜索到。这也是ES的近实时搜索。当索引保存到IndexBuffer中时,它不能被搜索到,直到它被刷新到一个段中。需要注意的是,Refresh有两种触发条件,其中一种是时间频率触发。默认是每1秒触发一次Refresh,可以通过index.refresh_interval设置。这就是为什么人们称Elasticsearch为近乎实时的搜索。另一种触发方式是在IndexBuffer满时触发Refresh。IndexBuffer大小的默认值是JVM占用内存容量的10%。Refresh后会将IndexBuffer中的数据写入Segment中,此时IndexBuffer中的数据会被清空。③ES每次刷新都会生成一个Segment文件,这样Segment文件就会越来越多。由于每个segment占用文件句柄、内存和CPU资源,假设每个搜索请求都会访问对应的segment获取数据,这意味着segment越多,搜索请求的负担就会增加,导致请求变慢。为了提高搜索性能,ES会周期性的对Segment进行一次Merge操作,即将多个小Segment合并为一个Segment。然后搜索请求直接访问合并段,从而提高搜索性能。④以上三步都是在内存中完成的,此时数据还没有写入磁盘。随着Segment的增加,内存空间有限,需要将数据写入磁盘。因此,合并完成后,新的段文件Flush会被写入磁盘。这时ES会创建一个CommitPoint文件,用于标识刷入磁盘的Segment。由于Segment是从内存commit到磁盘,所以记录时需要这个CommitPoint文件。它记录了Segment的去向,合并前的旧Segment和小Segment会从中移除。为此,CommitPoint会创建一个.del文件来存放被移除的Segment信息。需要注意的是,Flush的目的是为了持久化。毕竟Segment是保存在内存中的,总是保存到磁盘中。执行Flush时,会依次执行以下操作:IndexBuffer清空,CommitPointFilesystemBuffer中的记录通过fsync刷新到磁盘。translog被删除(translog将在后面详细描述)。达到一定大小(由index.translog.flush_threshold_size控制,默认512mb),也就是说ES会在满足上述条件时触发Flush。Elasticsearch数据存储可靠性上一节我们讲解了ES的文件合并刷新,分四步详细介绍了索引写入的过程。索引文档最初存储在内存的IndexBuffer中。当进行Refresh操作时,会保存为一个Segment,供用户查询。但是在Flush之前,Segment仍然存在于内存中。如果此时服务器宕机,ES还没有执行Flush操作,内存中保存的Segment数据就会丢失。为了提高ES的数据存储可靠性,引入了Translog。每次用户请求IndexBuffer执行操作时,都会向Translog中写入一条操作记录,Translog使用独特的机制保存到磁盘中。默认情况下,ES会在每次请求时将Translog同步到磁盘,即配置index.translog.durability为request。但是这会影响ES的性能,所以对于一些可以容忍数据丢失的场景,可以设置异步磁盘操作。可以将index.translog.durability配置为async,以提高写入Translog的性能,将translog异步写入磁盘。写入磁盘的频率由index.translog.sync_interval控制。另外,Translog随着请求数不断扩大,与Segment合并文件一样,需要整体存盘。这也是上一节提到的Flush操作。Flush操作将Segment放到磁盘上的同时,也会将Translog放到磁盘上。每次Flush之后,由于Translog完成,原来的Translog会被移除,在内存中重新创建一个新的Translog。由于附加了Translog,其性能还是比较优越的。当机器宕机时,ES服务启动时会读取Translog信息,回放中间操作命令恢复数据。继续上一节的例子,在原来的基础上增加Translog部分。如图3所示,在整个ES写入过程中加入了Translog,以提高ES数据存储的可靠性。图3:Translog简介图中Translog存在于内存和磁盘中,分别有两条线连接,表示Translog同步的两种方式:Translog是在ES处理用户请求时追加的,追加的内容是对ES的请求操作。此时操作记录的附加信息会根据配置同步或异步保存到磁盘中。Translog从内存到磁盘的另一个操作是在Flush发生的时候。如前一节所述,Flush操作会将Segment保存到磁盘中,同时也会将Translog文件放到磁盘中。刷新后存在于内存中的Translog将被删除。综上所述,本文重点介绍ES的编写过程。首先介绍ES的基本定义,然后从一个写请求开始介绍ES的集群和路由实现。ES会根据路由通过协调节点向主分片发送写请求,主分片再将数据同步到副本分片。然后介绍了对协调节点、主分片和副本分片的写操作。从宏观的角度理解了ES的写入过程后,再从微观的角度描述一下索引文档是如何通过内存写入磁盘的过程。索引文档写入IndexBuffer后,会通过Refresh操作保存到Filesystem的内存缓冲区中。这时候写好的索引文档会转化为一个Segment,可以被用户搜索到。同时,随着段数的增加,ES提供了合并机制,将多个小段合并成大段,从而提高搜索效率。为了提高ES数据存储的可靠性,会使用Translog机制追加ES请求命令,通过Flush机制将内存中的Segment和Translog放到磁盘上。作者:崔浩简介:十六年开发架构经验。曾在惠普武汉交付中心担任技术专家、需求分析师、项目经理,后在一家初创公司担任技术/产品经理。善于学习,乐于分享。目前专注于技术架构和研发管理。编辑:陶佳龙征稿:如有意向投稿或寻求报道,请联系editor@51cto.com【原创稿件请注明原作者和出处为.com,合作网站转载】
