技術(shù)干貨總結(jié):分布式系統(tǒng)常見同步機制
分布式系統(tǒng)為保證數(shù)據(jù)高可用,需要為數(shù)據(jù)保存多個副本,隨之而來的問題是如何在不同副本間同步數(shù)據(jù)?不同的同步機制有不同的效果和代價,本文嘗試對常見分布式組件的同步機制做一個小結(jié)。
常見機制
有一些常用的同步機制,對它們也有許多評價的維度,先看看大神的 經(jīng)典總結(jié) :

上圖給出了常用的同步方式(個人理解,請批評指正):
- Backup,即定期備份,對現(xiàn)有的系統(tǒng)的性能基本沒有影響,但節(jié)點宕機時只能勉強恢復
- Master-Slave,主從復制,異步復制每個指令,可以看作是粒度更細的定期備份
- Multi-Muster,多主,也稱“主主”,MS 的加強版,可以在多個節(jié)點上寫,事后再想辦法同步
- 2 Phase-Commit,二階段提交,同步先確保通知到所有節(jié)點再寫入,性能容易卡在“主”節(jié)點上
- Paxos,類似 2PC,同一時刻有多個節(jié)點可以寫入,也只需要通知到大多數(shù)節(jié)點,有更高的吞吐
同步方式分兩類,異步的性能好但可能有數(shù)據(jù)丟失,同步的能保證不丟數(shù)據(jù)但性能較差。同種方式的算法也能有所提升(如 Paxos 對于 2PC),但實現(xiàn)的難度又很高。實現(xiàn)上只能在這幾點上進行權(quán)衡。
考慮同步算法時,需要考慮節(jié)點宕機、網(wǎng)絡(luò)阻斷等故障情形。下面,我們來看看一些分布式組件的數(shù)據(jù)同步機制,主要考慮數(shù)據(jù)寫入請求如何被處理,期間可能會涉及如何讀數(shù)據(jù)。
Redis
Redis 3.0 開始引入 Redis Cluster 支持集群模式,個人認為它的設(shè)計很漂亮,大家可以看看 官方文檔 。
- 采用的是主從復制,異步同步消息,極端情況會丟數(shù)據(jù)
- 只能從主節(jié)點讀寫數(shù)據(jù),從節(jié)點只會拒絕并讓客戶端重定向,不會轉(zhuǎn)發(fā)請求
- 如果主節(jié)點宕機一段時間,從節(jié)點中會自動選主
- 如果期間有數(shù)據(jù)不一致,以最新選出的主節(jié)點的數(shù)據(jù)為準。
一些設(shè)計細節(jié):
- HASH_SLOT = CRC16(Key) mod 16384
- MEET
- WAIT
Kafka
Kafka 的分片粒度是 Partition,每個 Partition 可以有多個副本。副本同步設(shè)計參考 官方文檔
- 類似于 2PC,節(jié)點分主從,同步更新消息,除非節(jié)點全掛,否則不會丟消息
- 消息發(fā)到主節(jié)點,主節(jié)點寫入后等待“所有”從節(jié)點拉取該消息,之后通知客戶端寫入完成
- “所有”節(jié)點指的是 In-Sync Replica(ISR),響應太慢或宕機的從節(jié)點會被踢除
- 主節(jié)點宕機后,從節(jié)點選舉成為新的主節(jié)點,繼續(xù)提供服務(wù)
- 主節(jié)點宕機時正在提交的修改沒有做保證(消息可能沒有 ACK 卻提交了)
一些設(shè)計細節(jié):
- 當前消費者只能從主節(jié)點讀取數(shù)據(jù),未來可能會改變
- 主從的粒度是 partition,每個 broker 對于某些 Partition 而言是主節(jié)點,對于另一些而言是從節(jié)點
- Partition 創(chuàng)建時,Kafka 會盡量讓 preferred replica 均勻分布在各個 broker
- 選主由一個 controller 跟 zookeeper 交互后“內(nèi)定”,再通過 RPC 通知具體的主節(jié)點 ,此舉能防止 partition 過多,同時選主導致 zk 過載。
ElasticSearch
ElasticSearch 對數(shù)據(jù)的存儲需求和 Kafka 很類似,設(shè)計也很類似,詳細可見 官方文檔 。
ES 中有 master node 的概念,它實際的作用是對集群狀態(tài)進行管理,跟數(shù)據(jù)的請求無關(guān)。為了上下文一致性,我們稱它為管理節(jié)點,而稱 primary shard 為“主節(jié)點”, 稱 replica shard 為從節(jié)點。ES 的設(shè)計:
- 類似于 2PC,節(jié)點分主從,同步更新消息,除非節(jié)點全掛,否則不會丟消息
- 消息發(fā)到主節(jié)點,主節(jié)點寫入成功后并行發(fā)給從節(jié)點,等到從節(jié)點全部寫入成功,通知客戶端寫入完成
- 管理節(jié)點會維護每個分片需要寫入的從節(jié)點列表,稱為 in-sync copies
- 主節(jié)點宕機后,從節(jié)點選舉成為新的主節(jié)點,繼續(xù)提供服務(wù)
- 提交階段從節(jié)點不可用的話,主節(jié)點會要求管理節(jié)點將從節(jié)點從 in-sync copies 中移除
一些設(shè)計細節(jié):
- 寫入只能通過只主節(jié)點進行,讀取可以從任意從節(jié)點進行
- 每個節(jié)點均可提供服務(wù),它們會轉(zhuǎn)發(fā)請求到數(shù)據(jù)分片所在的節(jié)點,但建議循環(huán)訪問各個節(jié)點以平衡負載
- 數(shù)據(jù)做分片: shard = hash(routing) % number_of_primary_shards
- primary shard 的數(shù)量是需要在創(chuàng)建 index 的時候就確定好的
- 主從的粒度是 shard,每個節(jié)點對于某些 shard 而言是主節(jié)點,對于另一些而言是從節(jié)點
- 選主算法使用了 ES 自己的 Zen Discovery
Hadoop
Hadoop 使用的是鏈式復制,參考 Replication Pipelining
- 數(shù)據(jù)的多個復本寫入多個 datanode,只要有一個存活數(shù)據(jù)就不會丟失
- 數(shù)據(jù)拆分成多個 block,每個 block 由 namenode 決定數(shù)據(jù)寫入哪幾個 datanode
- 鏈式復制要求數(shù)據(jù)發(fā)往一個節(jié)點,該節(jié)點發(fā)往下一節(jié)點,待下個節(jié)點返回及本地寫入成 功后返回,以此類推形成一條寫入鏈。
- 寫入過程中的宕機節(jié)點會被移除 pineline,不一致的數(shù)據(jù)之后由 namenode 處理。
實現(xiàn)細節(jié):
- 實現(xiàn)中優(yōu)化了鏈式復制:block 拆分成多個 packet,節(jié)點 1 收到 packet, 寫入本地 的同時發(fā)往節(jié)點 2,等待節(jié)點 2 完成及本地完成后返回 ACK。節(jié)點 2 以此類推將 packet 寫入本地及發(fā)往節(jié)點 3……
TiKV
TiKV 使用的是 Raft 協(xié)議來實現(xiàn)寫入數(shù)據(jù)時的一致性。參考 三篇文章了解 TiDB 技術(shù)內(nèi)幕——說存儲
- 使用 Raft,寫入時需要半數(shù)以上的節(jié)點寫入成功才返回,宕機節(jié)點不超過半數(shù)則數(shù)據(jù)不丟失。
- TiKV 將數(shù)據(jù)的 key 按 range 分成 region,寫入時以 region 為粒度進行同步。
- 寫入和讀取都通過 leader 進行。每個 region 形成自己的 raft group,有自己的 leader。
Zookeeper
Zookeeper 使用的是 Zookeeper 自己的 Zab 算法(Paxos 的變種?),參考 Zookeeper Internals
- 數(shù)據(jù)只可以通過主節(jié)點寫入(請求會被轉(zhuǎn)發(fā)到主節(jié)點進行),可以通過任意節(jié)點讀取
- 主節(jié)點寫入數(shù)據(jù)后會廣播給所有節(jié)點,超過半數(shù)節(jié)點寫入后返回客戶端
- Zookeeper 不保證數(shù)據(jù)讀取為最新,但通過“單一視圖”保證讀取的數(shù)據(jù)版本不“回退”
小結(jié)
如果系統(tǒng)對性能要求高以至于能容忍數(shù)據(jù)的丟失(Redis),則顯然異步的同步方式是一種好的選擇。
而當系統(tǒng)要保證不丟數(shù)據(jù),則幾乎只能使用同步復制的機制,看到 Kafka 和 Elasticsearch 不約而同地使用了 PacificA 算法(個人認為可以看成是 2PC 的變種),當然這種方法的響應制約于最慢的副本,因此 Kafka 和 Elasticsearch 都有相關(guān)的機制將慢的副本移除。
當然看起來 Paxos, Raft, Zab 等新的算法比起 2PC 還是要好的:一致性保證更強,只要半數(shù)節(jié)點寫入成功就可以返回,Paxos 還支持多點寫入。只不過這些算法也很難正確實現(xiàn)和優(yōu)化。