天天看點

ElasticSearch Recovery 分析

另外你可能還需要了解下 recovery 階段遷移過程:

<b>init -&gt;  index -&gt; verify_index -&gt; translog -&gt; finalize -&gt; done</b>

recovery 其實有兩種:

primary的遷移/replication的生成和遷移 

primary的恢複

org.elasticsearch.indices.cluster.indicesclusterstateservice.clusterchanged 被觸發後,會觸發applyneworupdatedshards 函數的調用,這裡是我們整個分析的起點。大家可以跑進去看看,然後跟着文章打開對應的源碼浏覽。

閱讀完這篇文章,我們能夠得到:

熟悉整個recovery 流程

了解translog機制

掌握對應的代碼體系結構

這個是一般出現故障叢集重新開機的時候可能遇到的。首先需要從store裡進行恢複。

primary 進行自我恢複,是以并不需要其他節點的支援。是以判定的函數叫做ispeerrecovery 其實還是挺合适的。

indexservice.shard(shardid).recoverfromstore  調用的是 org.elasticsearch.index.shard.indexshard的方法。

邏輯還是很清晰的,判斷分片必須存在,接着将任務委托給 org.elasticsearch.index.shard.storerecoveryservice.recover 方法,該方法有個細節需要了解下:

es會根據restoresource 決定是從snapshot或者從store裡進行恢複。這裡的indexshard.recovering并沒有執行真正的recovering 操作,而是傳回了一個recover的資訊對象,裡面包含了譬如節點之類的資訊。

之後就将其作為一個任務送出出去了:

這裡我們隻走一條線,也就是進入 recoverfromstore 方法,該方法會執行索引檔案的恢複動作,本質上是進入了index stage.

接着進行translogrecovery了

這個方法裡面,最核心的是 internalperformtranslogrecovery方法,進入該方法後先進入 verify_index stage,進行索引的校驗,校驗如果沒有問題,就會進入我們期待的translog 狀态了。

進入translog 後,先進行一些設定:

這裡的gc 指的是tranlog日志的删除問題,也就是不允許删除translog,接着會建立一個新的internalengine了,然後傳回調用

不過你看這個代碼會比較疑惑,其實我一開始看也覺得納悶:

我們并沒有看到做translog replay的地方,而從上層的調用方來看:

performtranslogrecovery 傳回後,就立馬進入掃尾(finalizerecovery)階段。 裡面唯一的動作是createnewengine,并且傳遞了skiptranslogrecovery 參數。 也就說,真正的translog replay動作是在createnewengine裡完成,我們經過探索,發現是在internalengine 的初始化過程完成的,具體代碼如下:

裡面有個recoverfromtranslog,我們進去瞅瞅:

目前來看,所有的translog recovery 動作其實都是由 translogrecoveryperformer 來完成的。當然這個名字也比較好,翻譯過來就是 translogrecovery 執行者。先對translog 做一個snapshot,然後根據這個snapshot開始進行恢複,進入 recoveryfromsnapshot 方法我們檢視細節,然後會引導你進入下面的方法:

終于看到了實際的translog replay 邏輯了。這裡調用了标準的internalengine.create 等方法進行日志的恢複。其實比較有意思的是,我們在日志回放的過程中,依然會繼續寫translog。這裡就會導緻一個問題,如果我在做日志回放的過程中,伺服器由當掉了(或者es instance 重新開機了),那麼就會導緻translog 變多了。這個地方是否可以再優化下?

假設我們完成了translog 回放後,如果确實有重放,那麼就行flush動作,删除translog,否則就commit index。具體邏輯由如下的代碼來完成:

接着就進入了finalizerecovery,然後,就沒然後了。

一般這種recovery其實就是發生relocation或者調整副本的時候發生的。是以叢集是在正常狀态,一定有健康的primary shard存在,是以我們也把這種recovery叫做peer recovery。  入口和前面的primary恢複是一樣的,代碼如下:

核心代碼自然是 recoverytarget.startrecovery。這裡的recoverytarget的類型是:

startrecovery方法的核心代碼是:

也是啟動一個縣城異步執行的。recoveryrunner調用的是recoverytarget的 dorecovery方法,在該方法裡,會發出一個rpc請求:

這個時候進入 index stage。 那誰接受處理的呢? 我們先看看現在的類名叫啥? recoverytarget。 我們想當然的想,是不是有recoverysource呢? 發現确實有,而且該類确實也有一個處理類:

es裡這種通過netty進行互動的方式,大家可以看看我之前寫文章elasticsearch rest/rpc 接口解析。 

這裡我們進入recoversource對象的recover方法:

我們看到具體負責處理的類是recoverysourcehandler,之後調用該類的recovertotarget方法。我對下面的代碼做了精簡,友善大家看清楚。

首先建立一個translog的視圖(建立視圖的細節我現在也還沒研究),接着的話對目前的索引進行snapshot。 然後進入phase1階段,該階段是把索引檔案和請求的進行對比,然後得出有差異的部分,主動将資料推送給請求方。之後進入檔案清理階段,然後就進入translog 階段:

protected void preparetargetfortranslog(final translog.view translogview) {

接着進入第二階段:

對目前的translogview 進行一次snapshot,然後進行translog發送:

具體的發送邏輯如下:

這裡發的請求,都是被 recoverytarget的translogoperationsrequesthandler 處理器來完成的,具體代碼是:

這裡調用indexshard.performbatchrecovery進行translog 的回放。

最後發送一個finalizerecovery給target 節點,完成recovering操作。

關于recovery translog 配置相關

在如下的類裡有:

當伺服器恢複時發現有存在的translog日志,就會進入translog 階段進行replay。translog 的recovery 是走的标準的internalengine.create/update等方法,并且還會再寫translog,同時還有一個影響性能的地方是很多資料可能已經存在,會走update操作,是以性能還是非常差的。這個目前能夠想到的解決辦法是調整flush日志的頻率,保證存在的translog 盡量的少。 上面的話可以看出有三個控制選項:

本質上translog的恢複速度和條數的影響關系更大些,是以建議大家設定下 index.translog.flush_threshold_ops,比如多少條就一定要flush,否則積累的太多,出現故障,恢複就慢了。這些參數都可以動态設定,但建議放到配置檔案。