Elasticsearch节点选举、分片及Recovery

隔了挺长一段时间没有更新,主要是因为近段时间忙于业务和刷题,想来刷题除了Po题解和Explanation也是没有什么特别之处,除非钻研得特别深入,所以(@#$%^&找理由)。

关于Elasticsearch

Elasticsearch其实官网的文档特别齐全,所以关于用法没有什么特别好写的,看博客不如RTFM。但是文档特别全的情况下,很多时候又缺少对一些具体细节的描述,一句话说就是不知其所以然。所以今天写的博客内容理应是无关使用的,不涉及命令与操作,大概会更有意义一些吧。

概述

以Elasticsearch(下称ES)集群启动过程作为索引来展开,ES想要从Red转为Green,需要经历以下过程:

Bully算法与主节点选举

Bully算法

特地查了一下Bully的意思——“仗势欺人者,横行霸道者”,所以这个霸道选举算法如其名,简单暴力地通过选出ID最大的候选者来完成。在Bully算法中有几点假设:

它的选举通过以下几类消息:

设想以下场景,集群中存在ID为1、2、3的节点,通过Bully算法选举出了3为主节点,此时之前因为网络分区无法联系上的4节点加入,通过Bully算法成了新的主节点,后续失联的5节点加入,同样成为新主节点。这种不稳定的状态在ES中通过优化选举发起的条件来解决,当主节点确定后,在失效前不进行新一轮的选举。另外其他分布式应用一样,ES通过Quorum来解决脑裂的问题。

Elasticsearch主节点选举

ES的选举与Bully算法有所出入,它选举的是ID最小的节点,当然这并没有太大影响。另外目前版本中ES的排序影响因素还有集群状态,对应一个状态版本号,排序中会优先将版本号高的节点放在最前。

在选举过程中有几个概念:

某个节点ping所有节点,获取一份节点列表,并将自己加入其中。通过这份列表查看当前活跃的Master列表,也就是每个节点认为当前的Master节点,加入activeMasters列表中。同样,通过过滤原始列表中不符合Master资格的节点,形成masterCandidates列表

如果activeMasters列表不为空,按照ES的(近似)Bully算法选举自己认为的Master节点;如果activeMasters列表空,从masterCandidates列表中选举,但是此时需要判断当前候选人数是否达到Quorum。ES使用具体的比较Master的逻辑如下:

/**
 * compares two candidates to indicate which the a better master.
 * A higher cluster state version is better
 * 比较两个候选节点以得出更适合作为Master的节点。
 * 优先以集群状态版本作为排序
 *
 * @return -1 if c1 is a batter candidate, 1 if c2.
 * @c1更合适则返回-1,c2更合适则返回1
 */
public static int compare(MasterCandidate c1, MasterCandidate c2) {
    // we explicitly swap c1 and c2 here. the code expects "better" is lower in a sorted
    // list, so if c2 has a higher cluster state version, it needs to come first.
    // 先比较版本
    int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion);
    if (ret == 0) {
        // 比较节点
        ret = compareNodes(c1.getNode(), c2.getNode());
    }
    return ret;
}

/** master nodes go before other nodes, with a secondary sort by id **/
 private static int compareNodes(DiscoveryNode o1, DiscoveryNode o2) {
    if (o1.isMasterNode() && !o2.isMasterNode()) {
        // 如果o1是主节点
        return -1;
    }
    if (!o1.isMasterNode() && o2.isMasterNode()) {
        // 如果o2是主节点
        return 1;
    }
    // ID比较
    return o1.getId().compareTo(o2.getId());
}

确定之后进行投票,ES的投票是通过发送Join请求进行的,票数即为当前连接数。

如果临时Master为当前节点,则当前节点等待Quorum连接数,若配置时间内不满足,则选举失败,进行新一轮选举;若满足,发布新的clusterState。

如果临时Master节点不是本节点,则向Master发送Join请求,等待回复。Master如果得到足够票数,会先发布状态再确认请求。

主副分片选举与Allocation模块

分片的决策由Master节点完成,需要确认的内容包括:

allocators

Allocation模块中,allocators负责对分片作出优先选择,例如:

deciders

作出选择后,需要通过deciders判断分片是否真的可以指定在这个节点,例如:

主分片选举

分片经过指定节点后有allocation id,并且有inSyncAllocationIds列表记录哪些分片是处于“in-sync”状态的。主分片的选举通过是否处于in-sync列表来进行。

在历史版本中,分片有对应的版本号,但是如果使用版本号进行选举,如果拥有最新数据版本的分片还未启动,那么就会有历史版本的分片被选为主分片,例如只有一个活跃分片时它必定会被选为主分片。

通过将in-sync列表的分片遍历各个decider,如果有任一deny发生,则拒绝本次分配。决策结束之后可能会有多个节点,取第一个节点上的分片作为主分片。

分片模型

ES中使用Sequence ID标记写操作,以得到索引操作的顺序。现在考虑这种情况:由于网络原因,主分片产生的SID=145的操作转发到副分片上,但是没有传达成功,此时主分片被另一个副分片取代,也产生了一个SID=145(因为这个副分片最新的SID是144)的操作,转发给其他副分片。转发过程中,原来网络分区的主分片恢复,它的旧SID=145操作继续发送给其他副分片,那么分片副本中就有部分收到了旧主发的145操作,部分收到了新主发的145操作。

因此,除了Sequence ID以外,ES使用Primary Terms来标记主分片,每次新主分片产生时,Primary Terms加1,副分片会拒绝旧的Primary Terms发来的操作。

主节点为分片分配Primary Terms、Allocation ID,其中各个满足in-sync状态的分片的Allocation ID构成inSyncAllocationIds列表;Sequence ID由主分片为写操作分配,副分片拒绝Primary Terms+Sequence ID落后的操作。

分片数据Recovery

ES(大致的)存储模型在官网上有描述有图,所以就不多费时间描述了。

主分片Recovery

主分片因为处于in-sync list中,需要恢复的数据只有未进行fsync刷盘的部分,也就是refresh之后,变得可被索引,但是没有进行flush生成新的commit point持久化到磁盘的部分。这部分数据在translog中,因此需要将数据从translog进行恢复。

经过一系列的校验(是否主分片、分片状态是否异常等)工作后,从分片读取最后一次提交(commit)的段(segment)信息,获取其中版本号,更新当前索引版本。然后验证元信息中的checksum和实际值是否匹配,避免分片受损。

根据最后一次commit的信息,确认translog中哪些数据需要进行reply,执行具体的写操作,结束后进行refresh,和正常写操作一样,让数据转移到文件系统缓存中,变得可被索引到,但是没有fsync。

最后进行一次refresh更新分片状态,恢复完毕。

副分片Recovery

副分片恢复需要根据当前数据状态(进度)决定,如果Sequence ID满足,可以直接从主分片的Translog中恢复缺失部分;如果不满足,需要拉取主分片的Lucene索引和Translog进行恢复。

主分片一般先Recovery,结束后接受新业务的操作,如何保证副分片需要的Translog不清理?在最初的1.x版本中,ES阻止refresh操作保留translog,但是这样会产生很大的translog;在2.0-5.x版本中,引入了translog.view的概念,translog被分为多个文件,维护一个引用文件的列表,同时recovery通过translog.view获取这些文件的引用,因为文件引用的存在translog不能被清理,直到view关闭(没有引用)。6.0版本中引入了TranslogDeletingPolicy概念,维护活跃的translog文件,通过将translog做快照来保持translog不被清理。

副分片的恢复由两个阶段构成:

前面提过,如果可以基于SID进行恢复,跳过phase1;如果主副分片有同样的syncid且doc数相同,跳过phase1。

什么是syncid?当分片5分钟(可配置)没有写入操作就会被标记为inactive,执行synced flush,生成一个syncid,相同syncid意味着分片是相同的Lucene索引。

恢复过程中的主副分片一致性

恢复时,因为主副分片恢复时间不一致,主分片先进行Recovery,然后副分片才能基于主分片进行Recovery,所以主分片可以工作之后,副分片可能还在恢复中,此时主分片会向副分片发送写请求,因此恢复reply与主分片可能会同时(或者不按发生顺序)对同一个doc进行操作。ES中通过doc的版本号解决这个问题,当收到一个版本号低于doc当前版本号的操作时,会放弃本次操作。对于特定的doc,只有最新一次操作生效。

总结

Elasticsearch是个易用又复杂的分布式项目,其中很多分布式相关的设计和思想都值得学习和借鉴。在拉取代码时发现项目体积接近1GB:

uck@duck-MS-7A34:~/study/tmp$ du -sh elasticsearch/
949M    elasticsearch/

因此其中很多模块都没有了解清楚,希望以后可以保持学习的新鲜感,继续摸索更多的内容。