理解原理对于解决或避免集群维护过程中可能遇到的脑裂、无主、恢复慢、丢数据等问题很有帮助。
1 集群启动主要流程
1. Elect-master
选出临时Master【参选人数过半】
确定Master【得票过半】
检测集群节点数【离开节点】
2. Gateway
Master 索取MetaState
选举元信息,发布元信息
参与元信息选举的节点数过半
3. Allocation
向所有节点询问shard信息
磁盘且in-sync列表存在为主分片
磁盘存在选为副本否则延迟分配
4. Recovery
两阶段恢复
translog事件日志
2 Elect-master
集群选举,集群启动的第一件事:从已知的活跃机器列表中选择一个作为主节点。选主之后的流程由主节点触发。
选主算法:基于Bully算法进行了改进。
主要思路:对节点ID排序,取ID值最大的节点作为Master,每个节点都运行这个流程。简单来说就是基于节点ID排序的简单选举算法。
为什么不使用Raft算法,为什么不使用Paxos算法?
选主目的:确定唯一的主节点
3 Gateway
选举元信息,被选出的 Master 和集群元信息的新旧程度没有关系。因此它的第一个任务是选举元信息,让各节点把各自存储的元信息发过来,根据版本号确定最新的元信息,然后把这个信息广播下去,这样集群的所有节点都有了最新的元信息。
集群元信息的选举包括两个级别:集群级和索引级。不包含哪个shard存于哪个节点这种信息。这种信息以节点磁盘存储的为准,需要上报。为什么呢?因为读写流程是不经过Master的,Master 不知道各shard 副本直接的数据差异。
集群元信息选举完毕后,Master发布首次集群状态,然后开始选举shard级元信息。选举shard级元信息,构建内容路由表,是在allocation模块完成的。
4 allocation
在初始阶段,所有的shard都处于UNASSIGNED(未分配)状态。
ES中通过分配过程决定哪个分片位于哪个节点,重构内容路由表。此时,首先要做的是分配主分片。
1 分配主分片
主分片如何分配?
Master节点向集群中所有节点询问:所有节点将分片的元信息返回给Master,根据返回的 shard 信息,配合主节点内的分配策略选一个分片作为主分片。
这种做法效率怎么样?
询问量=shard 数×节点数。所以说我们最好控制shard的总规模别太大。有了shard的分片的多份信息,剩下的就是选出主分片。
主分片选举方式:
ES 5.x之前,通过对比shard级元信息的版本号来决定潜在问题:在多副本的情况下,考虑到如果只有一个 shard 信息汇报上来,则它一定会被选为主分片,但数据不一定是最新的。可能版本号比它大的那个shard所在节点还没启动。
ES 5.x之后:通过集群级元信息中记录的“最新主分片列表”确定主分片【汇报信息中存在,并且这个列表中也存在】
给每个 shard 都设置一个 UUID,然后在集群级的元信息中记录哪个shard是最新的。
因为ES是先写主分片,再由主分片节点转发请求去写副分片,所以主分片所在节点肯定是最新的。
主分片数量取决于什么?
2 分配副分片
主分片选举完成后,从上一个过程汇总的 shard 信息中选出副本。
如果汇总信息中不存在,则分配一个全新副本。分配的时间依赖于延迟配置项:index.unassigned.node_left.delayed_timeout
tip:我们的生产环境中最大的集群有100+节点,掉节点的情况并不罕见,很多时候不能第一时间处理,这个延迟我们一般配置为以天为单位。
allocation过程中允许新启动的节点加入集群。分片分配成功后进入recovery流程。
5 recovery
为什么需要recovery?
对于主分片来说,可能有一些数据没来得及刷盘
对于副分片来说,一是没刷盘,二是数据的不一致【主分片写完了,副分片还没来得及写,主副分片】
1 主分片recovery
将最后一次提交之后的 translog重放,建立Lucene索引,如此完成主分片的recovery
每次写操作都会记录translog【事务日志中记录了哪种操作以及相关数据】。
Lucene 的一次提交就是一次 fsync 刷盘的过程
2 副分片recovery
在ES的版本迭代中,副本分片的恢复策略有过不少调整,比较复杂。
副分片需要恢复成与主分片一致,同时,恢复期间还需要允许新的索引操作。
在6.0版本中,恢复分成两个阶段执行:
phase1:
在主分片所在节点,获取translog保留锁,从获取保留锁开始,会保留translog不受其刷盘清空的影响。
然后调用Lucene接口把已经刷磁盘中的shard做快照。然后把这些shard数据复制到副本节点。
在phase1完毕前,会向副分片节点发送告知对方启动engine
在phase2开始前,副分片就可以正常处理写请求了。
phase2:
对translog做快照,这个快照里包含从phase1开始,到执行translog快照期间的新增索引。
将这些translog发送到副分片所在节点进行重放。
思考:第二阶段运行期间,如果刷盘并清空tranlog怎么办?
3 恢复需重点关注4个问题
由于需要支持恢复期间的新增写操作(让ES的可用性更强),需要重点关注以下几个问题。
1. 分片数据完整性:如何做到副分片不丢数据?
第二阶段的 translog 快照包括第一阶段所有的新增操作。那么第一阶段执行期间如果发生“Lucene commit”(将文件系统写缓冲中的数据刷盘,并清空translog),清除translog怎办?
在ES 2.0之前:阻止了刷新操作,以此让translog都保留下来。
从ES 2.0之后:为了避免这种做法产生过大的translog,引入了translog.view的概念,创建 view 可以获取后续的所有操作。
从ES 6.0之后:translog.view 被移除,引入TranslogDeletionPolicy的概念,它将translog做一个快照来保持translog不被清理。这样实现了在第一阶段允许Lucenecommit。
2. 数据一致性
在ES 2.0之前:副分片恢复过程其实是三个阶段,第三阶段会阻塞新的索引操作,传输第二阶段执行期间新增的translog,这个时间很短。
在ES2.0之后:第三阶段被删除,恢复期间没有任何写阻塞过程。但是在副分片节点,重放translog时,phase1和phase2之间的写操作与phase2重放操作之间的时序错误和冲突【极少】。
通过写流程中异常处理,和对比版本号来过滤掉过期操作对于特定的 doc,只有最新一次操作生效,保证了主副分片的数据一致性
3. 第一阶段操作优化:第一阶段尤其漫长,因为它需要从主分片拉取全量的数据。
索引数据恢复是最漫长的过程。当shard总量达到十万级的时候,6.x之前的版本集群从Red变为Green的时间可能需要小时级。
在ES 6.x之后:增量同步,对第一阶段再次优化,标记每个操作。
在正常的写操作中,每次写入成功的操作都分配一个序号,通过对比序号就可以计算出差异范围然后同步。
ES 6.x中的translog恢复是一次重大改进,避免了从主分片所在节点拉取全量数据,为恢复过程节约了大量时间。
4. 集群启动过程中,集群健康值变化含义
当一个索引的主分片分配成功后,到此分片的写操作就是允许的。
当一个索引所有的主分片都分配成功后,该索引变为Yellow。
当全部索引的主分片都分配成功后,整个集群变为Yellow。
当一个索引全部分片分配成功后,该索引变为 Green。
当全部索引的索引分片分配成功后,整个集群变为Green。