ES集群的高可用可分为读高可用、写高可用与发生改变(集群状态改变)时高可用。其实这么说不是很准确,因为部分集群状态的改变会影响读和写的高可用。
读高可用指的是多个副本情况下,某个副本出问题时不影响整个系统的读。
写高可用指的是多个副本情况下,某个副本出问题时不影响整个系统的写,通过translog来确保数据不会丢失。
集群状态的改变的高可用包含自动处理节点的加入和离开,自动同步改变的集群状态,当集群发生故障时自动切换主副shard等等来保持集群的高可用。
读和写的高可用这里不再描述,下面将通过三个部分描述集群状态改变时的高可用。
1、集群状态同步
cluster state是全局性信息, 包含了整个群集中所有分片的元信息(规则,位置, 大小等信息), 并保持每个每节的信息同步。集群状态的详细信息可查看博客:https://cloud.tencent.com/developer/article/1134042。
cluster state是由ES的master节点维护的(只有主节点能够改变集群状态), 当它收到data节点的状态更新变化后, 就把这些信息依次广播到其他节点。目前我们一个集群的cluster state在60M左右,当集群状态改变的时候,主节点是否会广播整体的cluster state呢?同步集群状态的时候,ES会做些额外的处理:
- 只有变化的cluster state信息才会被广播
- 在cluster state传递之前或做些压缩
具体的过程如下所示:
1、主节点处理一个改变的集群状态,并将改变的状态publish给所有的其他节点。
2、其他节点收到主节点publish的message,向主节点确认收到信息(acknowledge。 it),但不将改变同步到本地的集群状态(not applay it)
3、如果主节点在配置的时间(discovery.zen.commit_timeout 默认30s)没有收到指定个数节点(discovery.zen.minimum_master_nodes)的确认信息,那么该改变的状态就会被rejected。
4、如果主节点在指定的时间内收到了指定个数节点的确认信息,则会提交(commit)该状态的改变,并向其他的节点发送该改变。
5、其他的节点收到信息后,则应用该改变到本地的集群状态中,应用后向主节点发送应用成功信息。
6、主节点等待所有节点的发送的应用成功消息,直到超时(discovery.zen.publish_timeout 默认30s)。
2、节点加入与离开
2.1、节点加入
当一个新节点加入的时候,它通过discovery.zen.ping.unicast.hosts配置的节点获取集群状态,然后找到master节点,并向其发送一个join request(discovery.zen.join_timeout)。主节点接收到reqest后,同步集群状态到新节点。
2.2、非主节点节点离开
当一个节点出现3次ping不通的情况时(ping_interval 默认为1s;ping_timeout 默认为30s),主节点会认为该节点已宕机,将该节点踢出集群。
2.3、主节点离开
当主节点发生故障时,集群中的其他节点将会ping当前的master eligible 节点,并从中选出一个新的主节点。
节点可以通过设置node.master为false来阻止自己变为一个主节点。
通过配置discovery.zen.minimum_master_nodes防止集群出现split brain。该配置通过检查集群中master eligible的个数来判断是否选举出一个主节点。其个数最好设置为(number_master eligible/2) 1,防止当主节点出问题时,一个集群分裂为两个集群。
具有最小编号的active master eligible node将会被选举为master节点。其详细选举过程请看博客:https://cloud.tencent.com/developer/article/1134031
3、分片副本同步
详细信息请看:https://www.easyice.cn/archives/243。
elasticsearch 使用 Allocation IDs 的概念,这是区分不同分片的唯一标识(UUIDS)。
Allocation IDs存储在 shard 级元信息中,每个 shard 都有自己的Allocation ID,同时集群级元信息中记录了一个被认为是最新shard 的Allocation ID集合,这个集合称为 in-sync allocation IDs。
如果由于网络或者其他原因,主副shard没有同步,那么副本的shard会将被从in-sync allocation IDs踢出。
举个例子:
个小型集群:一个主节点,两个数据节点。为了保持简单的例子,我们创建只有1个主分片和1个副分片的索引,最初,一个数据节点拥有主分片,另一个数据节点拥有副分片。我们使用 cluster state api 来查阅集群状态中的 in-sync 分片信息,并使用 “filter_path” query 参数来过滤出感兴趣的结果:
代码语言:javascript复制GET /_cluster/state?filter_path=metadata.indices.my_index.in_sync_allocations.*,routing_table.indices.my_index.*
result:
{
"metadata": {
"indices": {
"my_index": {
"in_sync_allocations": {
"0": [
"HNeGpt5aS3W9it3a7tJusg",
"wP-Z5fuGSM-HbADjMNpSIQ"
]
}
}
}
},
"routing_table": {
"indices": {
"my_index": {
"shards": {
"0": [
{
"primary": true,
"state": "STARTED",
"allocation_id": { "id": "HNeGpt5aS3W9it3a7tJusg" },
"node": "CX-rFmoPQF21tgt3MYGSQA",
...
},
{
"primary": false,
"state": "STARTED",
"allocation_id": { "id": "wP-Z5fuGSM-HbADjMNpSIQ" },
"node": "AzYoyzzSSwG6v_ypdRXYkw",
...
}
]
}
}
}
}
}
集群状态显示出主分片和副分片都已启动,主分片分配在数据节点 “CX-rFmo” ,副分片分配在数据节点 “AzYoyz”。他们都有唯一的allocation id,同时,也出现在in_sync_allocations集合中。
让我们看看当关闭主分片所在节点时会发生什么。由于这并不改变分片上的数据,所以两个分片副本应该保持同步。在没有主分片的情况下,副分片也应该被提示为主分片,这些都会反映在集群状态中:
代码语言:javascript复制{
"metadata": {
"indices": {
"my_index": {
"in_sync_allocations": {
"0": [
"HNeGpt5aS3W9it3a7tJusg",
"wP-Z5fuGSM-HbADjMNpSIQ"
]
}
}
}
},
"routing_table": {
"indices": {
"my_index": {
"shards": {
"0": [
{
"primary": true,
"state": "STARTED",
"allocation_id": { "id": "wP-Z5fuGSM-HbADjMNpSIQ" },
"node": "AzYoyzzSSwG6v_ypdRXYkw",
...
},
{
"primary": false,
"state": "UNASSIGNED",
"node": null,
"unassigned_info": {
"details": "node_left[CX-rFmoPQF21tgt3MYGSQA]",
...
}
}
]
}
}
}
}
}
由于只有一个数据节点,副分片停留在未分配状态。如果我们再次启动第二个节点,副分片将自动分配在这个节点上。为了使这个场景更有趣,我么不启动第二个节点,相反,我们索引一个文档到新提升的主分片中。由于分片副本现在是差异的(diverging),不活跃的哪个分片副本变为陈旧的,因此他的 ID被主节点从 in-sync 集合中删除:
代码语言:javascript复制{
"metadata": {
"indices": {
"my_index": {
"in_sync_allocations": {
"0": [
"wP-Z5fuGSM-HbADjMNpSIQ"
]
}
}
}
},
"routing_table": {
... // same as in previous step
}
}
现在只剩下一个同步的分片副本,让我们看看如果该副本变为不可用,系统如何处理。为此,我们关闭当前唯一的数据节点,然后启动前一个拥有陈旧分片副本的数据节点,之后,cluster health api 显示cluser health 为red,集群状态显示主分片尚未分配:
代码语言:javascript复制{
"metadata": {
"indices": {
"my_index": {
"in_sync_allocations": {
"0": [
"wP-Z5fuGSM-HbADjMNpSIQ"
]
}
}
}
},
"routing_table": {
"indices": {
"my_index": {
"shards": {
"0": [
{
"primary": true,
"state": "UNASSIGNED",
"recovery_source": { "type": "EXISTING_STORE" },
"unassigned_info": {
"allocation_status": "no_valid_shard_copy",
"at": "2017-01-26T09:20:24.054Z",
"details": "node_left[AzYoyzzSSwG6v_ypdRXYkw]"
},
...
},
{
"primary": false,
"state": "UNASSIGNED",
"recovery_source": { "type": "PEER" },
"unassigned_info": {
"allocation_status": "no_attempt",
"at": "2017-01-26T09:14:47.689Z",
"details": "node_left[CX-rFmoPQF21tgt3MYGSQA]"
},
...
}
]
}
}
}
}
}
让我们再看看cluster allocation explain API ,这是一个调试分配问题的好工具。 运行不带参数的explain命令将提供系统找到的第一个未分配分片的说明:
代码语言:javascript复制GET /_cluster/allocation/explain
explain API 告诉我们为什么主分片处于未分配状态,同时还提供了基于每个节点上的更详细的分配信息。在这个例子中,主节点在集群当前可用节点中无法找到同步的(in-sync)分片副本。
代码语言:javascript复制{
"index" : "my_index",
"shard" : 0,
"primary" : true,
"current_state" : "unassigned",
"unassigned_info" : {
"reason" : "NODE_LEFT",
"at" : "2017-01-26T09:20:24.054Z",
"last_allocation_status" : "no_valid_shard_copy"
},
"can_allocate" : "no_valid_shard_copy",
"allocate_explanation" : "cannot allocate because all found copies of the shard are either stale or corrupt",
"node_allocation_decisions" : [
{
"node_id" : "CX-rFmoPQF21tgt3MYGSQA",
"node_name" : "CX-rFmo",
"transport_address" : "127.0.0.1:9301",
"node_decision" : "no",
"store" : {
"in_sync" : false,
"allocation_id" : "HNeGpt5aS3W9it3a7tJusg"
}
}
]
}
该API还显示在节点“CY-rFmo”上可用的分片副本是陈旧的( store.in_sync = false )。启动拥有in-sync 分片副本的那个节点将使集群重新变为 green。如果那个节点永远都回来了呢?
reroute API 提供了一个子命令 allocate_stale_primary ,用于将一个陈旧的分片分配为主分片。使用此命令意味着丢失给定分片副本中缺少的数据。如果同步分片只是暂时不可用,使用此命令意味着在同步副本中最近更新的数据。应该把它看作是使群集至少运行一些数据的最后一种措施。在所有分片副本都不存在的情况下,还可以强制Elasticsearch使用空分片副本分配主分片,这意味着丢失与该分片相关联的所有先前数据。 不言而喻,allocate_empty_primary 命令只能用于最糟糕的情况,其含义很好理解。
4、其他介绍
4.1、Pending Task
当集群出问题时,我们在cat health时会看到pending task,pending task到底是什么东西?
只有master节点能处理集群元数据层面的改变任务。大多数情况下,master可以处理,但当集群元数据改变的速度超过了master节点处理的速度时,将会导致这些元数据操作的任务被缓存入队列中,即pending tasks。pending task API 将会显示队列中被挂起的所有的集群元数据改变的任务。
当cluster state 太大的时候,一些改变很容易更新其cluster state,更新一次cluster state会消耗很多cpu还有传送到其他节点的网络带宽,会花费较多的时间。