什么是Cluster State
顾名思义,Cluster State即集群状态,与常用的"yellow/red/green"等单词表示的集群健康状态不同,ElasticSearch集群(下面简称ES)的Cluster State是指集群中的各种状态和元数据(meta data)信息。其中,索引(Index)的mappings、settings配置、持久化状态等信息,以及集群的一些配置信息都属于元数据(meta data)。
元数据非常重要,标识了集群中节点状态、索引的配置与状态信息等;假如记录某个index的元数据丢失,那么集群就认为这个index不再存在。ES集群中的每个节点都会保存一份这样的元数据信息,这样在增删索引、节点场景下能够极大方便集群选主流程、集群的管理操作。
Cluster State的主要内容
Cluster State中存储的主要内容如下:
long version: 当前版本号,每次更新加1,即便集群重启version仍会增加
String stateUUID:该state对应的唯一id
ClusterName clusterName:集群名称
DiscoveryNodes nodes:当前集群全部节点信息
RoutingTable routingTable:集群中所有index的路由表,即集群中全部索引的各个分片在集群节点上的分布信息
MetaData metaData:集群的meta数据,主要包括所有索引的mappings和settings配置
ClusterBlocks blocks:集群级的限制设定,用于屏蔽某些操作
ImmutableOpenMap<String, Custom> customs: 自定义配置,如snapshots,可用插件扩展
ES提供了Cluster state API,可通过_cluster/state
来获取集群中的全部状态信息,具体使用可参考ES Cluster State API官方文档 。下面以微医搜索的一个ES集群的Cluster State进行实例说明(删除部分数据):
{
"cluster_name": "search-ng-es",
"version": 2374,
"state_uuid": "rnTdpJFEQKajoJmu7KtndA",
"master_node": "ma2DN4tdS0aUyh11eefcFw",
"blocks": {},
"nodes": { //节点详细信息
"ma2DN4tdS0aUyh11eefcFw": {
"name": "1.250",
"ephemeral_id": "HESWTTuZR8GONJ9GuqYy3g",
"transport_address": "192.168.1.250:9800",
"attributes": {
"ml.max_open_jobs": "10",
"ml.enabled": "true"
}
},
"k3EhLIwPQJCLD5xfpeheXw": {
"name": "1.251",
"ephemeral_id": "T3PIwu1ES3Wjq0_QPkdVQA",
"transport_address": "192.168.1.251:9800",
"attributes": {
"ml.max_open_jobs": "10",
"ml.enabled": "true"
}
}
},
"metadata": { //元数据信息
"cluster_uuid": "nldSAAbhTMikYpYPvBWOrQ",
"templates": {...}, // 模板设置
"indices": { //索引元数据
"sc-cloud-search-plat-service-2020-01-01": {
"state": "open",
"settings": {...},
"mappings": {...},
"aliases": [],
"primary_terms": {
"0": 1, //代表这个shard的primary切换的次数,用于区分新旧primary
"1": 1,
"2": 1
},
"in_sync_allocations": {
"0": [ // 拥有最新数据的allocation,若primary丢失,就从该列表中选出新的主。若节点恢复后且其id仍在该列表中,则认为数据没有缺少,不做恢复。
"ePd2g6gjQ2iSvJaaF0zm3Q",
"5tuGUEYyS3W7J6P-yhFdmQ"
],
...
}
},
}
"ingest": { // ingest节点的处理pipeline
"pipeline": [...]
},
"index-graveyard": {
"tombstones": [ //已删除的索引的墓碑,默认500个,防止节点重新加入集群后不知道索引已删掉
{
"index": {
"index_name": "sc-cloud-search-plat-service-2019-09-21",
"index_uuid": "gGdBx7sZTAObnUXtBDVP0A"
},
"delete_date_in_millis": 1570237201705
},
...
]
}
},
"routing_table": { //每个索引的每个shard的具体路由信息
"indices": {
"sc-cloud-search-plat-service-2019-12-24": {
"shards": {
"0": [
{
"state": "STARTED", //共四种状态,UNASSIGNED, INITIALIZING, STARTED, RELOCATING
"primary": false,
"node": "k3EhLIwPQJCLD5xfpeheXw",
"relocating_node": null, //如果state为RELOCATING状态,这个值表示relocating的目标节点
"shard": 0,
"index": "sc-cloud-search-plat-service-2019-12-24",
"allocation_id": {
"id": "pz4-yNB-QRKTNEbCfVMldQ" //当前allocation的uid,和in_sync_allocations对应
}
},
...
],
...
}
},
...
}
}
"routing_nodes": { //这个就是把routing_table按照node级梳理后的结果
"unassigned": [],
"nodes": {
"k3EhLIwPQJCLD5xfpeheXw": [
{
"state": "STARTED",
"primary": false,
"node": "k3EhLIwPQJCLD5xfpeheXw",
"relocating_node": null,
"shard": 0,
"index": ".watcher-history-6-2019.03.02",
"allocation_id": {
"id": "9WpabxbMRm68aGwunNUFKA"
}
},
...
]
}
}
}
Cluster State的更新流程
ES源码中,集群状态的对应类为ClusterState.java,下面是该类的部分注释说明。
The cluster state object is immutable with the exception of the {@link RoutingNodes} structure, which is built on demand from the {@link RoutingTable}.
The cluster state can be updated only on the master node. All updates are performed by on a single thread and controlled by the {@link ClusterService}. After every update the {@link Discovery#publish} method publishes a new version of the cluster state to all other nodes in the cluster. The actual publishing mechanism is delegated to the {@link Discovery#publish} method and depends on the type of discovery.
基于上述说明,总结ClusterState相关特性如下:
- ClusterState是不可变对象,每次状态变更都会产生新的ClusterState,版本号随之更新
- 在ES中, 集群状态由Master节点维护,并且只能由Master节点更新集群状态
- Master节点一次处理一批集群状态更新,计算所需的更改并将更新后的新版集群状态发布到集群中的所有其他节点
Cluster State的更新流程,实现了原子性和一致性,确保Cluster State能够反映集群中最新且真实的状态,为Master选举流程、错误检测、集群扩缩容提供了高度的一致性保障。下面来详细分析:
- 原子性保证:master节点进程内的不同线程更改ClusterState时,每次需提交一个Task给MasterService,MasterService中只使用一个线程来串行处理这些Task,每次处理时把当前的ClusterState作为Task中execute函数的参数,即保证了所有的Task都是在currentClusterState的基础上进行更改,然后不同的Task是串行执行的。
- 一致性保证:
一致性
是为了解决这样一个问题,我们知道,新的集群状态一旦在某个节点上commit,那么这个节点就会执行相应的操作,比如删除某个Shard等,这样的操作是不可回退的。而假如此时Master节点挂掉了,新产生的Master一定要在新的集群配置信息上进行更改,不能出现回退,否则就会出现集群配置数据回退了但是操作无法回退的情况;ES使用两阶段提交方式(Add two phased commit to Cluster State publishing)实现一致性
。
所谓的两阶段提交,是把Master节点发布ClusterState分成两步,第一步是向所有节点推送最新的ClusterState,当有超过半数的master节点返回ack请求时,再发送commit请求,要求节点commit接收到的新版ClusterState。如果没有超过半数的节点返回ack,则认为本次发布失败,同时退出master状态,执行rejoin重新加入集群。
基于原子性和一致性更新保证,分析Cluster State的更新流程如下图所示:
每次发布时,
- 首先由Master节点向集群中的所有节点广播新版本的集群状态;
- 其他节点接收到新版集群状态后,均向Master节点发送ack确认请求,但此时并不立即应用新接收的集群状态。
- 一旦Master节点收到足够多的候选Master节点的确认请求,就认为新版集群状态已提交(committed),继而广播commit请求,指示其他节点应用现在已提交的集群状态。
- 每个节点接收到commit请求后,会把新版ClusterState发给该节点上相关的各个模块,各个模块根据新版ClusterState判断是否要做什么操作,如创建Shard等;应用新版集群状态后,再将第二个ack确认请求发送回Master节点。
- 在Master节点开始处理并发布下一版本集群状态更新前,它一直处于等待状态,直到到达超时时间或它收到集群中每个节点已应用新版集群状态的确认请求。其中,新版集群状态完全发布到所有节点的超时时间,由
cluster.publish.timeout
这一配置设置,默认为从发布开始算起的30s。如果在提交新版集群状态之前已达到该超时时间,则集群状态更改失败,并且Master节点认为退出Master状态,并重新加入集群,开始尝试选举新的Master节点。如果新版集群状态在到达cluster.publish.timeout
指定的超时时间前已提交,则Master节点认为更改已成功。 - 如果没有收到某些节点确认已应用当前新版集群状态的请求,则这些节点被认为滞后节点,因为它们的集群状态已落后于Master节点的最新状态。Master节点等待滞后节点再追赶
cluster.follower_lag.timeout
设定的时间,默认为90s。如果节点在该时间段内仍未成功应用集群状态更新,则认为该节点已失败并将之从集群中删除。
另外,集群状态更新通常以与之前集群状态的差异量的形式发布,从而减少发布集群状态更新所需的时间和网络带宽。例如,当仅更新集群状态中部分索引的mappings时,如果其他节点已经拥有这部分索引的集群状态信息,则只需更新这些索引的变更信息即可。如果某个节点缺少先前的集群状态信息(例如,重新加入集群场景下),则Master节点将向该节点发布完整的集群状态,以便其可以接收将来的差异更新。
注:在ES 7.x版本之前,ES使用Gossip + Bully算法进行选主流程。Bully算法在具体实现中,通过比较ClusterState的版本,版本高的优先当选。(各个版本的ES实现有所不同,但都需要考虑候选节点的集群状态版本)。但这一算法无法做到完全的一致性,在一定情况下会出现脑裂。ES 7.x版本开始,ES实现了raft算法,该算法引入任期(term)的概念,彻底解决了脑裂问题,具体内容在此不再赘述,可阅读参考文档。
小结
本文概述了Cluster State的构成与作用,深入分析了Cluster State的更新流程;Cluster State在ES集群管理、选主流程、一致性保证上有举足轻重的作用,值得深入研究,希望能够抛砖引玉,吸引大家对ES的进一步深入使用