ElasticSearch 1.6数据同步策略的一个优化

2015年07月14日 作者: 查超

问题背景和描述

问题背景

ElasticSearch

ElasticSearch是一个基于Apache Lucene的实时分布式搜索和分析引擎,用于全文搜索、结构化搜索、分析以及将这三者混合使用。可以这样描述它:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

ElasticSearch 1.6.0 release 点击下载

Apache Lucene

上面说到ES基于Apache Lucene,Lucene是一个全文检索引擎架构,提供了完整的查询引擎和索引引擎。它是一个高性能、可伸缩的信息搜索(IR)库,Lucene是用java实现的、成熟的开源项目。

相关概念

Elasticsearch有很多核心概念,下面对几个相关的概念进行简单说明:

cluster:集群,是由多个节点组织在一起,共同持有数据并一起提供索引和搜索功能。

node:节点,是集群中的一个服务器,集群的一部分,存储数据并参与集群的索引和搜索功能。

index:索引,ES存储数据的地方,类似于关系数据库的database。

shard:分片,索引被分成许多shards,每个分片本身也是一个功能完善并且独立的“索引”,所有这些分片分布在不同节点上,也就是不同机器上面。

replicas: ES允许创建分片的拷贝,称为副本。每个主分片还有自己的几个副本分片。 作为一个故障转移或者优化机制:

  • 分片或节点失败的情况下,提供了高可用性。
  • 扩展搜索量和吞吐量,使请求可以在所有的复制上并行运行。

各个概念之间的关系图

ElasticSearch中集群节点分片之间的关系

如图1所示,cluster和node都是物理层面上的概念,一个集群包含很多节点。而shard分为两种,一种是主分片primary,一种是副本replica。主分片数量决定了index被分成多少部分,而replica是主分片的备份,数据和主分片完全一致。所有这些shard分别存储在不同的node上,尽量使得副本和主分片不在同一个节点上。

与Lucene segment之间的关系

从底层结构来看,每个Shard实际就是一个Luene的Index,index又是由Segment组成,segment里面可以简单理解为一个字典,记录多个term在原文的位置。 如图2所示:

问题描述

下面我们描述一下出现的问题。一个cluster中,有节点node加入或退出时ES都会根据机器的负载对分片进行重新分配到节点上。分配的目标是副本和主分片一致,如果节点已有副本,但不一致,则通过网络复制。对于一致性的判断,老版本偷懒直接比较的是Shard整体的MD5码是否相同,这往往造成很多“误判”。这样一次数据恢复的过程非常的缓慢,实际应用中会给公司带来很大的麻烦。

新版本1.6添加了一项新的ID,对每个segment中的内容进行标识,有效的解决了这一问题。具体实现方法下面会对它们进行说明。

问题出现的原因

实际上一个Lucene Index有多少个Segment是不固定的,和写入速度相关。一开始因为高写入速度会导致Segment过多,这种碎片化长久下去会导致查询速度降低,所以Lucene有后台进程异步把多个segment合并成一个。正是因为Lucene段合并机制导致不同shard中的segment即使内容相同,它们之间的MD5也不一样。

解决方法

参考信息:ElasticSearch 1.6.0 released

新的同步机制

ElasticSearch 1.6.0中添加了一个新的同步刷新机制synced-flush。该功能是把一个sync_id写进主分片和复制分片用来标记它们,ID相同则segment内容必然相同。程序会直接忽略比较segment内容,而只是比较它们原先设定的标记id,这本身就在很大程度上节省了时间,同样也避免了由于“误判”带来的无用复制。可以说这一改进极大的增强了Recovery的速度。

上面说到的sync_id标记了主分片和副本上具有相同内容的segment,这一过程是依赖flush来完成的。一个synced flush过程会发生在任何空闲索引上,从一开始就确保对相同内容的segment进行标记。这尤其对于日志记录来说又非常地有用,之前的index会在索引停止5分钟之后自动同步。

如果我们需要重启一个节点或集群而并不想等待同步自动发生,可以这样做:

  • 停止索引的过程,同时等待正在进行的请求停止
  • 停止分配shard(分片)
  • 主动发出一个syncd-flush(同步冲洗)请求
  • 重启节点
  • 重新分配shard
  • 等待集群状态,直到它们都是绿色
  • 恢复索引

Code Analysis

在这里,对代码的逻辑进行简单的叙述

删除seal机制

首先想要说一下seal密封,这是之前旧版本ES Recovery过程中用到的密封机制,它提供同步刷新的内部逻辑和手动停止刷新的API。现在为了避免和新版本用到的刷新机制造成冲突,删除所有之前seal相关的API。

重写flush机制

flush机制首先为所有副本做刷新处理,然后为它们写下sync_id。程序为了确保同步刷新之后每个包含相同内容的shard副本拥有相同sync id,按照下面三步来完成:

1,先flush所有主分片还有对应的副本,同时把它们的commit id都收集到一起

2,确保主分片上没有正在执行的索引操作

3,为每个副本shard做一次额外的flush,并为它们写下sync id

处理第三步需要满足下面限定条件:

a,目标副本自从上一次flush之后没有任何未提交的更改

b,而且a中说到的上一次flush也必须是上面步骤1的flush,也就是用于收集commit id的那次

根据上面三步思路,在"src/main/java/org/elasticsearch/indices/flush/"路径下新建SyncedFlushService.class类,定义

attemptSyncedFlush(ShardId,ActionListener<ShardsSyncedFlushResult>)

方法,获取shard列表并且获取commit id。

发送同步请求给每个分片:

sendSyncRequests(syncId, activeShards, state, commitIds, shardId, totalShards, actionListener)

完成之后调用

sendPreSyncRequests(activeShards, state, shardId, commitIdsListener)

方法发送第二次flush请求,为每个副本进行二次刷新操作。

解决方法的分析比较

就上面说到的旧版本和新版本的数据恢复方法,笔者对它们进行如下比较:

  • 时间方面:旧办法由于判断错误,造成大量数据需要复制,非常缓慢;新办法只是比较id,不会造成大量无用复制,恢复时间很快

  • 准确度方面:旧办法能有效地找出所有和主分片内容不同的segment,准确度应该很高的,但是由于底层设计原因,错误的把很多不需要复制的segment拿来复制,反而降低了准确度;新办法在每次flush之后都会有sync_id生成,从一开始就比较了segment,准确度也很好。

ES1.7版最新改进

上面说到的都是1.7以前版本对recovery过程的处理,而在刚刚发布的最新版本ES1.7当中,又对indices的同步复制问题做了进一步优化。

参考信息:Elasticsearch 1.7.0 and 1.6.1 released

添加优先权属性

假设下面一种情况出现:服务器断电,ES整个集群全部需要重启和数据恢复。此时,如果我们急需某个index上面的数据,但是又有如此多的old data需要恢复,而恢复的过程是随机的,因此我们不得不等待需要的那块数据恢复完,这会耽误我们的时间。

新的改进版本,为每个index添加一项priority属性,代表该索引在需要数据恢复时候的优先权。priority的值是一个非负整数,数值越大优先权越高,优先权越高也就越先被恢复。index完全按照优先权大小按顺序recovery,优先权可以由以下几项决定:

  • 我们可以自己设置index的priority属性
  • 默认按照index的创建时间决定priority值的大小
  • 默认按照index的名称决定priority的大小

如果不做任何更改的话,最新创建的index会被优先recovery,当然我们也可以自己设置priority的值来增加老数据的优先级。

更改方法

该设置可以在一个运行中的index上完成,甚至当indices正在recovery的时候也能实时更新。

PUT important_index/_settings
{
    "index.priority": 5
}

后记

ElasticSearch节点重启带来的数据复制问题,一直是公司迫切需要解决的难题之一。新办法提出一个synced-flush处理过程,大大的节省了Recovery时间。

另外,问题的关键就在底层的Lucene merge导致segment不同,实际上Lucene 5.0.0中已经对段进行了改进,给每一个segment添加了一个commit_id。笔者在这里思考的是能否通过这个改进,通过直接比较主分片和副本中每个segment的commit id来解决上面所述的问题。关于Lucene的改进文档,详情请点击:Lucene 5.0 change log-5895

在这里不得不吐槽,ES有些地方做的还是有不少问题,公司目前正使用并且研究ES,会对遇到的很多问题进行汇总和研究改进,以后会不断有更多相关的技术分析,欢迎大家一起讨论。