Elasticsearch跨叢集同步

35220579發表於2018-06-04

高可用架構

ZSearch是目前公司內最大的Elasticsearch服務平臺,隨著業務的深入,越來越多的關鍵鏈路使用者對資料的可用性和容災能力提出更高的需求,而在這塊領域 社群一直沒有完整的解決策略,原生的 Snapshot And Restore 只能做快照的恢復,不能做到實時同步;業內主流的佇列分發模式(通過訊息佇列快取請求資料,多個叢集消費資料實現請求複製)也只能做到請求的同步,一個不可預期的操作如delete_by_query 叢集間便會產生資料差異。歸根到底我們需要一個類似Mysql的binlog同步方案, 從底層機制上保證資料的最終一致性,為此 ZSearch核心團隊經過數月原始碼研究 精心打造了Elasticsearch-XDCR,一款基於translog原生複製協議的跨叢集同步產品。

Elasticsearch-XDCR

圖片名稱

elasticsearch-xdcr 採用外掛形式,產品形態上依然保留了ES社群一貫的簡單風格。

  1. 安裝簡單,只要在兩個叢集安裝外掛重啟即可。
  2. 使用簡單,兩個restful操作完成資料同步。
  3. 資料一致,基於原生複製協議,支援實時同步。
  4. 穩定方便,支援mapping自動更新以及斷點續傳。

使用樣例

第一步 在主叢集建立備份叢集倉庫

PUT _xdcr/{cluser_name} -d {
       "settings": {
           "seeds": [{node_1}, {node_2}, ... ]
      }
}

第二步 開啟索引同步

_xdcr/{cluster_name}/{job_name} -d `
{
  "indices": "index_1,index_2 ..."
}
`

原始碼實現

本文將通過Elasticsearch原始碼分析,結合其原生複製處理過程來講解xdcr的設計思路,首先我們瞭解ES的一些基礎概念。

  • 一份索引(index)可分成多個分片(shard)並分配在不同節點上(node) 。
  • 一份shard可由一個主分片(primary)和多個備副本(replica)組成。
  • 資料先寫到主分片,寫入成功則同步到各個備副本上。

elasticsearch-xdcr的實現思路便是分析primary到replica的內部複製處理流程,將複製目標擴充套件到外部叢集達成跨叢集資料同步效果。

叢集排程模型

  • 類似於k8s|hippo,ES的分散式排程模型也採用一種目標式的資源管理方式,即master釋出一個全域性狀態(ClusterState),工作節點共同協作,直到整體叢集狀態達到一致。
  • RoutingTable(路由表)是狀態的一個子模組,他管理了所有索引的路由狀態,如新建的索引放在那些節點上,某些節點磁碟快滿了應該遷移出去等等。
  • ShardRouting 是RoutingTable的具體表現,他表示一個分片的路由規則,如分片是否為主分片、放在哪個節點、現在處於什麼狀態等等。

當我們為一個索引建立一個新的副本,Master節點便會釋出一個新的叢集狀態,被分配的Work節點根據ShardRouting找到主分片位置並建立恢復任務,此過程在ES中被稱之為peer_recovery。

核心原始碼指引: org.elasticsearch.indices.cluster.IndicesClusterStateService

“推模式”索引拷貝流程

瞭解了總的處理流程,我們可以把關注點聚焦到主-副間的具體恢復流程。ES採用的是一種推模式,主分片在接收到副本節點發來的start_recovery請求,會源源不斷的往目標分片推送索引資料,直到全部結束後,備節點將shard更新到可服務狀態 完成peer_recovery流程,推的過程分為三個階段:

  • phase1: 同步索引檔案,已經構建好的最終索引檔案。
  • phase2: 同步translog,需要做恢復形成最終索引檔案。
  • finalize: 同步checkpoint,更新全域性提交點,保持和主分片的Sequnce位點同步。

原始碼及呼叫關係參照下圖

核心原始碼指引
org.elasticsearch.indices.recovery.*

跨叢集改造

ES的原生PeerRecovery過程只能在索引的建立初始化階段(init),而跨叢集方案中外部叢集通常已經建好了對等的索引,處於服務狀態(started)的shard不允許做任何後臺任務。最初的方案我們是想設計一種新的ShardRouting,在索引核心載入層擴充套件新的Recovery型別支援遠端recovery,但這樣的改動會侵入到ES core層原始碼,不利於後續的版本迭代。如果想無侵入以外掛形式實現跨叢集複製,只能利用二階段的translog recovery,shard在服務狀態translog可偽裝成primary操作寫入引擎,但是純translog recovery在速度上遠不及索引檔案拷貝,經過各種利弊權衡,我們最終還是採用零侵入的translog方式,畢竟不綁架使用者的產品更具生命力。

程式碼片段

Engine.Operation.Origin.PEER_RECOVERY -> Engine.Operation.Origin.PRIMARY 

for (Translog.Operation operation : operations) {
    Engine.Result result = shard.applyTranslogOperation(operation, Engine.Operation.Origin.PRIMARY, update -> {
        mappingUpdater.updateMappings(update, shard.shardId(), ((Translog.Index) operation).type());
        throw new ReplicationOperation.RetryOnPrimaryException(shard.shardId(), "Mapping updated");
    });
}

SequnceNumber & 斷點續傳

ES 6.0之後引入了一個非常重要的特性 SequnceNumber,shard上任何型別的寫操作,包括index、create、update和delete,都會生成一個連續遞增的_seq_no。這使得Node異常或重啟時,能夠從最後的位點(checkpoint)快速恢復,這個特性同樣也使的跨叢集複製具備了第二個重要條件 – 斷點續傳 。利用該特性,在多機房容場景抵禦各種不可預知的問題,如網路抖動、網路故障、叢集當機等等,恢復後的recovery流依然可以通過seqNo快速恢復。

程式碼片段

Translog.Snapshot snapshot = translog.newSnapshotFromMinSeqNo(startingSeqNo);

結尾

由於篇幅有限,本文介紹了實現ES跨叢集同步的兩個重要條件和實現思路,後期可結合思路詳細講解實現的過程。


相關文章