ElasticSearch集群災難:別放棄,也許能再搶救一下
Elasticsearch作為一個分布式搜索引擎,自身是高可用的;但也架不住一些特殊情況的發(fā)生,如:
集群超過半數(shù)的master節(jié)點丟失,ES的節(jié)點無法形成一個集群,進而導致集群不可用;
索引shard的文件損壞,分片無法被正?;謴?,進而導致索引無法正常提供服務
本地盤節(jié)點,多數(shù)據(jù)節(jié)點故障,舊節(jié)點無法再次加入集群,數(shù)據(jù)丟失
針對上述的情況,今天來聊一聊相關的解決方案。
2 基礎知識
2.1 集群經典架構
在聊解決方案之前,首先來看一看ES集群層面的基本知識,es的集群組成通常如圖1-1所示
圖 1-1 es常用集群架構
如圖1-1所示,為生產環(huán)境es集群的經典架構,主要由專有主節(jié)點、專有協(xié)調節(jié)點和數(shù)據(jù)節(jié)點組成:
- 專有主節(jié)點(Master-eligible node): 具有master角色的節(jié)點,這使其有資格被選為主節(jié)點,只存儲集群元信息包含cluster、index、shard級別的元數(shù)據(jù);該種角色節(jié)點被選舉為master之后,將作為整個ES集群的大腦,負責維護集群層面的元信息,創(chuàng)建刪除索引等工作。該種節(jié)點的個數(shù)必須為奇數(shù),通常我們固定為3個,如果該類節(jié)點丟失半數(shù),es集群將無法維持es節(jié)點形成一個集群。
- 專有協(xié)調節(jié)點(網關節(jié)點): 該種節(jié)點不具有任何角色,僅僅用來處理es請求;比如(1)將寫請求的數(shù)據(jù)歸類轉發(fā)到數(shù)據(jù)所屬的節(jié)點(2)查詢請求的二次聚合計算。通常我們也會給該類節(jié)點保留ingest角色 ,ingest的主要作用是對數(shù)據(jù)進行預處理;比如:字段重命名、給數(shù)據(jù)文檔打上指紋和清洗數(shù)據(jù)等功能主要通過pipeline能力進行處理
- 數(shù)據(jù)節(jié)點(Data node): 存儲數(shù)據(jù)和集群元信息,執(zhí)行與數(shù)據(jù)相關的操作,如CRUD、搜索和聚合。在數(shù)據(jù)節(jié)點上打上不同的屬性,可以使其成為hot、warm、cold數(shù)據(jù)節(jié)點,在es7.9版本之后配置略有不同,但是原理基本不變。
如果沒有顯示設置節(jié)點角色,es的每個節(jié)點都會含有以上三種角色。除此之后還有Remote-eligible node 、ml-node和Transform nodes等角色需要顯示的配置,節(jié)點才會有該角色。
2.2 集群元信息
集群完全啟動主要包含選舉主節(jié)點、元信息、主分片、數(shù)據(jù)恢復等重要階段;如圖2-1所示[1]。
圖 2-1 es集群啟動流程
主節(jié)點選舉的過程,不是本文的重點,而是集群元信息的選舉。被選舉出的master和集群元信息新舊程度沒有關系;master節(jié)點被選舉出來之后,它所要完成的第一個任務,即是選舉集群元信息。
(1)Master選舉成功之后,判斷其持有的集群狀態(tài)中是否存在STATE_NOT_RECOVERED_BLOCK,如果不存在,則說明元數(shù)據(jù)已經恢復,跳過gateway恢復過程,否則等待。org.elasticsearch.gateway.GatewayService#clusterChanged
//跳過元數(shù)據(jù)恢復
if (state.blocks().hasGlobalBlock(STATE_NOT_RECOVERED_BLOCK) == false) {
// already recovered
return;
}
//此處省略部分代碼。
//進入gateway恢復過程
performStateRecovery(enforceRecoverAfterTime, reason);
(2)Master從各個節(jié)點主動獲取元數(shù)據(jù)信息。org.elasticsearch.gateway.Gateway#performStateRecovery
# 獲取元信息核心代碼
final String[] nodesIds = clusterService.state().nodes().getMasterNodes().keys().toArray(String.class);
logger.trace("performing state recovery from {}", Arrays.toString(nodesIds));
final TransportNodesListGatewayMetaState.NodesGatewayMetaState nodesState = listGatewayMetaState.list(nodesIds, null).actionGet();
(3)從獲取的元數(shù)據(jù)信息中選擇版本號最大的作為最新元數(shù)據(jù);元信息包括集群級、索引級。
## org.elasticsearch.gateway.Gateway#performStateRecovery
public void performStateRecovery(final GatewayStateRecoveredListener listener) throws GatewayException {
# 省略若干行代碼
## 進入allocation階段;
## final Gateway.GatewayStateRecoveredListener recoveryListener = new GatewayRecoveryListener();
## listener為 GatewayStateRecoveredListener
listener.onSuccess(builder.build());
}
(4)兩者確定之后,調用allocation模塊的reroute,對未分配 的分片執(zhí)行分配,主分片分配過程中會異步獲取各個shard級別元數(shù)據(jù)。
#主要實現(xiàn)方法為如下方法
#org.elasticsearch.gateway.GatewayService.GatewayRecoveryListener#onSuccess
## 主要工作是構建集群狀態(tài)(ClusterState),其中的內容路由表 依賴allocation模塊協(xié)助完成,調用 allocationService.reroute 進 入下一階段:異步執(zhí)行分片層元數(shù)據(jù)的恢復,以及分片分配。updateTask線程結束.
ES中存儲的數(shù)據(jù):(1)state元數(shù)據(jù)信息;(2)index Lucene生成的索引文件;(3)translog事務日志。元數(shù)據(jù)信息:
- nodes/0/_state/*.st,集群層面元信息MetaData(clusterUUID 、 settings 、templates等);
- nodes/0/indices/{index_uuid}/_state/*.st,索引層面元信息IndexMetaData( numberOfShards 、mappings等);
- nodes/0/indices/{index_uuid}/0/_state/*.st,分片層面元信息ShardStateMetaData(version 、indexUUID、primary等)。
上述信息被持久化到磁盤:持久化的state不包括某個分片存在于哪個節(jié)點這種內容路由信息,集群完全重啟時,依靠gateway的recovery過程重建RoutingTable和RoutingNode。當讀取某個文檔時, 根據(jù)路由算法確定目的分片后,再從RoutingTable中查找分片位于哪個節(jié)點,然后將請求轉發(fā)到目的節(jié)點[1]。
?? 注意:在es7.0.0之后es的元信息存儲方式發(fā)生變化;es7.0.0之后元信息存儲改使用lucene的方式存儲,見pr50928 Move metadata storage to Lucene)
7.10.2 專有主節(jié)點,集群元數(shù)據(jù)
./
|-- _state
| |-- _39h.cfe
| |-- _39h.cfs
| |-- _39h.si
| |-- node-0.st
| |-- segments_50d
| `-- write.lock
`-- node.lock
6.8.13 專有主節(jié)點,集群元數(shù)據(jù)
./
|-- _state
| |-- global-230.st
| `-- node-2.st
|-- indices
| |-- -hiy4JnoRfqUJHTJoNUt4Q
| | `-- _state
| | `-- state-4.st
| `-- ylJKVlqISGOi8EkpxHE_2A
| `-- _state
| `-- state-6.st
`-- node.lock
3 災難場景與處理方法
3.1 master節(jié)點丟失
?? 注意本文所述的master節(jié)點個數(shù),假設前提均為3個
場景1 master節(jié)點丟失過半
master節(jié)點是控制整個集群;當該種節(jié)點角色丟失過半,由于集群中投票節(jié)點永遠不可能達到quorum無法選主,將無法維持es節(jié)點形成一個集群;雖然集群無法形成一個集群,但所仍幸master-eligible節(jié)點存活,我們可以使用如下手段進行處理。
es7.0.0版本之前
- 修改剩余節(jié)點的elasticsearch.yaml配置如下,修改quorum的個數(shù),然后啟動剩余的節(jié)點,形成一個新的集群;
discovery.zen.minimum_master_nodes: 1
discovery.zen.ping.unicast.hosts:
- masters-0
- 重建補充之前丟失的master-eligible節(jié)點,加入集群之后.3 將集群配置修改為舊的配置,再逐一重啟下集群中的節(jié)點,先從master-eligible開始.
es7.0.0(包含)版本之后.
在es7.0.0版本之后,由于es修改集群的啟動配置,新增配置discovery.seed_hosts 和cluster.initial_master_nodes;es集群第一次啟動時稱為bootstrap,該過程將配置文件中的cluster.initial_master_node作為初始的投票節(jié)點Voting configurations,投票節(jié)點具有選舉master和commit cluster state的權利,超過半數(shù)以上同意即投票成功。如果在集群健康的場景下,我們需要下線超過半數(shù)的master-eligible;則必須首先使用投票配置排除API從投票配置中排除受影響的節(jié)點。
POST _cluster/voting_config_exclusions?node_names={node_names}
POST _cluster/voting_config_exclusions?node_ids={node_ids}
DELETE _cluster/voting_config_exclusions
但是如果丟失的master節(jié)點超過半數(shù),則可以使用新的集群處理工具elasticsearch-node unsafe-bootstrap pr37696 和elasticsearch-node detach-cluster pr37979
面對丟失半數(shù)master-eligible,es7.0.0(包含)版本之后的處理步驟如下:1 使用bin/elasticsearch-node unsafe-bootstrap命令讓唯一主節(jié)點以不安全的方式改寫投票節(jié)點,就像重新進行bootstrap一樣,自己使用持久化的cluster state形成一個新集群2 其他數(shù)據(jù)節(jié)點無法加入新集群因為UUID不同(es使用UUID作為節(jié)點和集群的唯一表示,每個節(jié)點都會持久化當前集群的UUID),使用bin/elasticsearch-node detach-cluster命令讓節(jié)點離開之前的集群3 啟動數(shù)據(jù)節(jié)點和新的master-eligible節(jié)點(如下補充兩個新的master-eligible),他會加入新集群中
cluster.initial_master_nodes:
- {master-0}
- {new-master-1}
- {new-master-2}
discovery.seed_hosts:
- {master-ip-0}
- {new-master-ip-1}
- {new-master-ip-2}
場景2 master節(jié)點全部丟失
es7.0.0版本之前
1 關閉 security 功能(如果開啟了, 最好先關閉security插件功能):
1.1 因為新啟動的master節(jié)點, 沒有數(shù)據(jù)節(jié)點(如果只配置了一個master的角色), security插件的初始化無法完成, 各類接口不好調用
1.2 如果給新啟動的master節(jié)點, 配置了master and data角色, 則security插件會初始化成功. 會插入index, 但是這個index會和原來的data節(jié)點上保存的沖突. 不知道怎么解.elastic官方xpack-security;關閉鑒權:xpack.security.enabled:false2 啟動足夠的新master-eligible節(jié)點形成一個新集群.
discovery.zen.minimum_master_nodes: 2
discovery.zen.ping.unicast.hosts:
- {new-masters-1}
- {new-masters-2}
- {new-masters-3}
3 修改數(shù)據(jù)節(jié)點的為新master的地址,并且刪除掉節(jié)點上的_state(因為新集群的cluster UUID不一致),同上
4 啟動數(shù)據(jù)節(jié)點,數(shù)據(jù)被恢復加入到集群
es7.0.0(包含)版本之后
已經沒有cluster state了,唯一的希望是數(shù)據(jù)節(jié)點上的index數(shù)據(jù);恢復方式借助elasticsearch-node 工具
1 關閉security功能(如果開啟了, 最好先關閉security插件功能),原因同上
2 啟動足夠的新master-eligible節(jié)點形成一個新集群
cluster.initial_master_nodes:
- {new-master-0}
- {new-master-1}
- {new-master-2}
discovery.seed_hosts:
- {new-master-ip-0}
- {new-master-ip-1}
- {new-master-ip-2}
3 bin/elasticsearch-node detach-cluster命令讓數(shù)據(jù)節(jié)點離開之前的集群
./bin/elasticsearch-node detach-cluster
------------------------------------------------------------------------
WARNING: Elasticsearch MUST be stopped before running this tool.
------------------------------------------------------------------------
You should only run this tool if you have permanently lost all of the
master-eligible nodes in this cluster and you cannot restore the cluster
from a snapshot, or you have already unsafely bootstrapped a new cluster
by running `elasticsearch-node unsafe-bootstrap` on a master-eligible
node that belonged to the same cluster as this node. This tool can cause
arbitrary data loss and its use should be your last resort.
Do you want to proceed?
Confirm [y/N] y
Node was successfully detached from the cluster
4 查詢dangling索引,GET /_dangling, 改api 引入es7.9版本于 pr581765 啟動數(shù)據(jù)節(jié)點并使用Import dangling indexAPI將index數(shù)據(jù)import到cluster state中(官方推薦,es7.9版本之后). 或者 配置gateway.auto_import_dangling_indices: true引入于es7.6版本pr49174(es7.6.0-7.9.0可用該配置,在7.6版本之前不需要配置默認加載dangling索引)并啟動數(shù)據(jù)節(jié)點
POST /_dangling/{index-uuid}?accept_data_loss=true
6 導入完成之后,索引recovery之后即可進行讀寫
注意
Q1: 為什么7.6.0之后需要配置,才能處理懸空索引(dangling index)才能讓數(shù)據(jù)加入新集群,7.6.0之后沒有懸空索引嗎?A1: 其實也是有的,只不過在es2版本將配置移除(對應pr10016),默認自動加載dangling index(es2.0-es7.6); 具體實現(xiàn)于org.elasticsearch.gateway.DanglingIndicesState#processDanglingIndices es7.6再次引入dangling配置,es7.9引入dangling index rest api
Q2: 什么是 dangling 索引?A2: 當一個節(jié)點加入集群時,如果發(fā)現(xiàn)存儲在其本地數(shù)據(jù)目錄中的任何分片(shard)不存在于集群中,將認為這些分片屬于“懸空”索引。懸空索引產生的場景(1)在 Elasticsearch 節(jié)點離線時刪除了多個cluster.indices.tombstones.size 索引,節(jié)點再次加入集群集群 (2)master節(jié)點丟失,數(shù)據(jù)節(jié)點重新加入新的集群等
3.2 數(shù)據(jù)節(jié)點故障
數(shù)據(jù)節(jié)點災難故障之后,無法恢復加入集群;可將數(shù)據(jù)物理復制到新的節(jié)點,然后按照master節(jié)點丟失的方式,將數(shù)據(jù)節(jié)點加入集群即可。
3.3 分片不能夠自動分配
查看索引分片為什么無法分配,POST_cluster/allocation/explain
3.3.1 分片正常
如果分片數(shù)據(jù)正常,那么我們可以嘗試重試分配分片任務;POST _cluster/reroute?retry_failed
獲取索引的shard在那些節(jié)點上,使用_shard_stores api
GET indexName1/_shard_stores
使用cluster reroute重新分配
# 嘗試分配副本
POST /_cluster/reroute
{
"commands": [
{
"allocate_replica": {
"index": "{indexName1}",
"shard": {shardId},
"node": "{nodes-9}"
}
}
]
}
如果是主分片無法分配,可以嘗試如下命令進行分配
POST /_cluster/reroute
{
"commands": [
{
"allocate_stale_primary": {
"index": "{indexName1}",
"shard": {shardId},
"node": {nodes-9},
"accept_data_loss": true
}
}
]
}
如果主分片確實是無法分配,只能選擇丟失該分片的數(shù)據(jù),分配一個空的主分片
POST /_cluster/reroute
{
"commands": [
{
"allocate_empty_primary": {
"index": "{indexName1}",
"shard": {shardId},
"node": "{nodes-9}",
"accept_data_loss": true
}
}
]
}
es5.0版本之前參考;https://www.elastic.co/guide/en/elasticsearch/reference/2.4/cluster-reroute.html
3.3.2 分片數(shù)據(jù)損壞
shard corrupted
錯誤參考Corrupted elastic index
shard-tool es6.5版本引入,該操作需要stop節(jié)點elasticsearch-shard 工具es6.5版本引入 pr33848elasticsearch-shard remove-corrupted-data 的 es7.0.0引入 pr32281
bin/elasticsearch-shard remove-corrupted-data --index {indexName} --shard-id {shardId}
## 示列:修復索引twitter的0號分片
bin/elasticsearch-shard remove-corrupted-data --index twitter --shard-id 0
## 如果--index和--shard-id換成索引分片目錄參數(shù)--dir,則直接修復data和translog
bin/elasticsearch-shard remove-corrupted-data --dir /var/lib/elasticsearchdata/nodes/0/indices/P45vf_YQRhqjfwLMUvSqDw/0
修復完成之后,啟動節(jié)點,如果分片不能夠自動分配,使用reroute命令進行shard分片
POST /_cluster/reroute{
"commands":[
{
"allocate_stale_primary":{
"index":"index42",
"shard":0,
"node":"node-1",
"accept_data_loss":false
}
}
]}
5版本之前可以通過索引級別配置,進行修復index.shard.check_on_startup: fix ,該配置在es6.5版本移除 pr32279
translog 損壞
修復translog操作,需要stop節(jié)點。
修復工具 elasticsearch-translog es5.0.0 引入pr19342elasticsearch-shard remove-corrupted-data translog的 es7.4.1開始引入,pr47866elasticsearch-shard 可以直接清除translog,也可以像上文中指定--dir那樣進行修復translog
bin/elasticsearch-shard remove-corrupted-data --index --shard-id --truncate-clean-translog
## 示列:修復索引twitter的0號分片
bin/elasticsearch-shard remove-corrupted-data --index twitter --shard-id 0 --truncate-clean-translog
清除完成之后使用cluster reroute 進行恢復
5版本之前可以通過索引級別配置,進行修復index.shard.check_on_startup: fix ,該配置在es6.5版本移除 pr32279
segments_N文件丟失
該種場景的文件損壞是最難修復的;官方還未提供工具,我們正在自己調研中
4 參考
[1] elasticsearch集群啟動流程
[2]https://www.elastic.co/guide/en/elasticsearch/reference/7.9/dangling-indices-list.html
[3]https://www.elastic.co/guide/en/elasticsearch/reference/7.10/node-tool.html