這幾天剛實操了一把shard 的relocation, 在cerebro 上看著圖標移來晃去覺得很爽,但之前并沒深究,也沒讀過這部分的代碼。前幾晚夜間上線需要重啟所有的ES機器,大家也對ES的relocation 討論了一番,這次就順便讀讀這部分的代碼。
這里不再討論shard 在分配的一些算法,和一些基礎概念,如果對分片如何分到一個節點的算法有興趣,或者對一些概念還不熟悉的話,建議可以看下面文章,或者我之前的文章:
Elasticsearch 5.x 源碼分析(7)Shard Allocation 的一些小細節
elasticsearch源碼分析之Allocation模塊
這次打算用一個例子來貫穿大部分的allocation的入口,如下圖:
先假設我們有三個節點,并創建了一個index,2分片2replica。0p 和1r 坐落在Node1 上,1p 和0r 坐落在Node2, 現在我們先假設Node1 Node2都shutdown了,現在這4個分片都是
UNassigned
狀態。
1. 重啟,初始化分配
這里先撇開集群把shard 進行飄移的問題,簡單看一下shard是怎么 assign 的,node1 和node2 啟動之前,在集群里面,分片0 和分片1 都會出于 UNassigned
狀態;當Node1啟動完,它會主動去ping Master節點,在前面的文章都知道了,這時會完整data node的注冊的所有步驟,同時Master會下發最新的clusterState 下來。然后在Node1 內部會觸發ClusterStateChangeEvent
,這個事件會觸發多個modules或者Service去處理邏輯,這里我們關心的邏輯在 IndicesClusterStateService
里。
當處理到檢測分配到自己的shard是否存在時,Node1 會檢測這個shard的狀態,如沒有,則會去創建一個shard,有的話則會檢測是否需要更新狀態
由于Node1 剛剛啟動,它還沒有去加載 0p 和1r,因此會進入createShard()流程,并在后面跑去加載它本機的shard,這部分的邏輯在createShard() 里面的indicesService.createShard()中
最后Node1 啟動完后會把0p 和1r 標記成
initializing
并上報給Master,這樣Master就知道這個shard已經被assigned
了并標記成initialzing
了。這里介紹的是一個正向的例子,那么一些反向的例子,假如,在Node1 啟動的時候,其實過了很久了,0p早就已經分配到其他機器了,那么Master 發過來的
ClusterState
中的0p 就已經是assigned狀態了,那么這段邏輯就不是在 createOrUpdateShards(state);
處理了,而是之前的removeUnallocatedIndices(state)
,failMissingShards(state)
和removeShards(state)
來處理,大家有興趣可以分別過過這兩個代碼。
現在回到這個正向的例子,稍等片刻之后,Node1 已經完全初始化完0p 和1r 了,那么node1 就會把這兩個 shard 置成started
狀態,等下一個時間間隔的ClusterState
心跳過來時,顯而易見就會進入updateShard(nodes, shardRouting, shard, routingTable, state);
在這里Node1 就會主動發送一個Action 給master 來請求 做
startedShard
這個Action(最后一行)。在
ShardStateAction
里,有兩種請求需要處理,分別是
ShardStartedClusterStateTaskExecutor
ShardFailedClusterStateTaskExecutor
分別處理啟用或者mark fail,其中我們看看ShardStartedClusterStateTaskExecutor
的邏輯
從代碼看出,這里最終要的一個地方就是,并不是 node上報什么狀態,Master就會不分青紅皂白一味記錄,它需要通過
allocationService.applyStartedShards()
來自己去校驗和消費這個狀態,最后則生成一個新的ClusterState
,這會體現在下一次的心跳下發。
而ShardFailedClusterStateTaskExecutor
則是處理Node上報的failShard的事情,這種情況我覺得比較少見
經過這一輪后,Node1 上的所有shards都恢復正常,那么Node2 也按這個流程,最后Node1 Node2 啟動完后就是下圖
2. Node1 掛了,Master標記0p,1r
剛剛上面說到,ShardFailedClusterStateTaskExecutor
這種主動上報一般是比較少見的,我們見更多一般是Node1 重啟或者進程掛掉,從
我們曾經提過,ZenDiscovery用于處理一切的Node事件變更,那我們就找找這段代碼
繼續跟下去,這次我們只關心和shard allocation 相關的代碼,處理Node fail 的話會執行一個NodeRemovalClusterStateTaskExecutor.Task
在這里我們找到了我們想找到的東西
這里就會把這臺機的所有shard進行 failShard()處理,也就是會mark UNassigned 等等。那么在下一次的
ClusterState
同步事件時其他所有節點就會知道了
3. Cluster Reroute
有時候運維需要,比如我們這次需要全部升級ES,那么往往我們需要手工的去鎖住禁止集群進行 allocation,rebalance 等,甚至我們在加機器之后我們希望是手工自己去分配分片,那么就要用到cluster Reroute。相對應的Action是RestClusterRerouteAction
和TransportClusterRerouteAction
,而最后會是由 allocationService.reroute()
來承載,在這個例子里,加入我手工吧0p從Node1 移動到Node3 去,看會發生什么事情。
上面代碼最重要就是那句
RoutingExplanations explanations = commands.execute(allocation, explain);
在MoveAllocationCommand
中會對將要Reroute的操作進行一系列的判斷,比如canAllocate()
仔細留意的話,這里的targetRelocatingShard 其實是一個
PeerRecoverySource
的shard,這是就會把這個shard標記成INITIALIZING
狀態。
那這個狀態就會在ClusterState 中并下次心跳同步到目標節點去。也就是說Node3 在下次獲取ClusterState時,就會得到新的ShardRouting 請求了。
這時Node3 還是回到 第1章中的方法里,只是這時Node3 并沒有0r這個Shard,并且,Node3就會開始嘗試從Node1 處去回復這個Shard了
后話
我寫這篇文章的初衷是,我當時很好奇,Node3 是如何恢復0p的呢,因為0p好歹是個Primary 呀,并且這時,Node1 會繼續服務嗎?
繼續服務這個是肯定的,至于Node3是如何恢復的,我的一個猜測是首先Node3會像恢復一個replica 那樣把Node1的分片拷貝過去,接著,在這段時間的增量數據將會繼續同步translog 的方式同步到Node3,到最后,直到同步到一個最新的點,這時Node1 和Node3的分片都是同步的,然后就直接做個切換,0p就成功切換到Node3去了,關于Recovery這塊我沒有再繼續讀下去了,知道的朋友歡迎也順便告知我一下。
由于Allocation這部分代碼非常復雜,因此我也沒有完全弄懂,有些點也是我自己的推測而成,如有錯誤歡迎指正和討論。