高可用的升級-RocketMQ知識體系7
一直以來,在多地多中心的消息發(fā)送場景下,如何保障數(shù)據(jù)的完整性和一致性是一個技術(shù)難點。在 RocketMQ 4.5 版本之前,RocketMQ 只有 Master/Slave 一種部署方式,一組 broker 中有一個 Master ,有零到多個
Slave,Slave 通過同步復制或異步復制的方式去同步 Master 數(shù)據(jù)。Master/Slave 部署模式,提供了一定的高可用性。
但這樣的部署模式,有一定缺陷。比如故障轉(zhuǎn)移方面,如果主節(jié)點掛了,還需要人為手動進行重啟或者切換,無法自動將一個從節(jié)點轉(zhuǎn)換為主節(jié)點。那么什么樣的多副本架構(gòu)可以來解決這個問題呢?首先我們來看看多副本技術(shù)的演進。
多副本技術(shù)的演進
Master/Slave
多副本最早的是 Master/Slave 架構(gòu),即簡單地用 Slave 去同步 Master 的數(shù)據(jù),RocketMQ 最早也是這種實現(xiàn)。分為同步模式(Sync Mode)和異步模式(Async Mode),區(qū)別就是 Master 是否等數(shù)據(jù)同步到 Slave 之后再返回 Client。這兩種方式目前在 RocketMQ 社區(qū)廣泛使用的版本中都有支持,也可以看我前面分享的文章。
基于 Zookeeper 服務
隨著分布式領(lǐng)域開啟了快速發(fā)展。在 Hadoop 生態(tài)中,誕生了一個基于 Paxos 算法選舉 Leader 的分布式協(xié)調(diào)服務 ZooKeeper。
由于 ZooKeeper 本身擁有高可用和高可靠的特性,隨之誕生了很多基于 ZooKeeper 的高可用高可靠的系統(tǒng)。
具體做法如下圖所示:
Based on Zookeeper/Etcd
如圖所示,假如系統(tǒng)里有 3 個節(jié)點,通過 ZooKeeper 提供的一些接口,可以從 3 個節(jié)點中自動的選出一個 Master 來。選出一個 Master 后,另外兩個沒成功的就自然變成 Slave。選完之后,后續(xù)過程與傳統(tǒng)實現(xiàn)方式中的復制一樣。故基于 ZooKeeper 的系統(tǒng)與基于 Master/Slave 系統(tǒng)最大的區(qū)別就是:選 Master 的過程由手動選舉變成依賴一個第三方的服務(比如 ZooKeeper 或 Etcd)的選舉。
但是基于 ZooKeeper 的服務也帶來一個比較嚴重的問題:依賴加重。因為運維 ZooKeeper 是一件很復雜的事情。
基于 Raft 服務方式
因為 ZooKeeper 的復雜性,又有了以下 Raft 的方式。Raft 可以認為是 Paxos 的簡化版。基于 Raft 的方式如下圖 4 所示,與上述兩種方式最大的區(qū)別是:leader 的選舉是由自己完成的。比如一個系統(tǒng)有 3 個節(jié)點,這 3 個節(jié)點的 leader 是利用 Raft 的算法通過協(xié)調(diào)選舉自己去完成的,選舉完成之后,Master 到 Slave 同步的過程仍然與傳統(tǒng)方式類似。最大的好處就是去除了依賴,即本身變得很簡單,可以自己完成自己的協(xié)調(diào)
實現(xiàn)高可靠和高可用的方法優(yōu)劣對比
Master/Slave,Based on ZooKeeper/Etcd 和 Raft,這三種是目前分布式系統(tǒng)中,做到高可靠和高可用的基本的實現(xiàn)方法,各有優(yōu)劣。
Master/Slave
優(yōu)點:實現(xiàn)簡單
缺點:不能自動控制節(jié)點切換,一旦出了問題,需要人為介入。
基于 Zookeeper/Etcd
優(yōu)點:可以自動切換節(jié)點
缺點:運維成本很高,因為 ZooKeeper 本身就很難運維。
Raft
優(yōu)點:可以自己協(xié)調(diào),并且去除依賴。
缺點:實現(xiàn) Raft,在編碼上比較困難。
多副本架構(gòu)首先需要解決自動故障轉(zhuǎn)移的問題,本質(zhì)上來說是自動選主的問題。這個問題的解決方案基本可以分為兩種:
- 利用第三方協(xié)調(diào)服務集群完成選主,比如 zookeeper 或者 etcd。這種方案會引入了重量級外部組件,加重部署,運維和故障診斷成本,比如在維護 RocketMQ 集群還需要維護 zookeeper 集群,并且 zookeeper 集群故障會影響到 RocketMQ 集群。
- 利用 raft 協(xié)議來完成一個自動選主,raft 協(xié)議相比前者的優(yōu)點是不需要引入外部組件,自動選主邏輯集成到各個節(jié)點的進程中,節(jié)點之間通過通信就可以完成選主。
目前很多中間件都使用了raft 協(xié)議或使用了變種的raft協(xié)議,如mongodb .還有新版的kafka,放棄了zookeeper,
將元數(shù)據(jù)存儲在 Kafka 本身,而不是存儲 ZooKeeper 這樣的外部系統(tǒng)中。新版kafka的Quorum 控制器使用新的 KRaft 協(xié)議來確保元數(shù)據(jù)在仲裁中被精確地復制。這個協(xié)議在很多方面與 ZooKeeper 的 ZAB 協(xié)議和 Raft 相似。
RocketMQ也選擇用 raft 協(xié)議來解決這個問題。
關(guān)于Raft
我們知道在分布式領(lǐng)域,始終都要面臨的一個挑戰(zhàn)就是:數(shù)據(jù)一致性.
Paxos。如今它是業(yè)界公認此類問題的最有效解。雖然Paxos在理論界得到了高度認可,但是卻給工程界帶來了難題。因為這個算法本身比較晦澀,并且抽象,缺少很多實現(xiàn)細節(jié)。這讓許多工程師大為頭疼
Raft是為解決Paxos難以理解和實現(xiàn)的問題而提出的。
Raft 算法的工作流程主要包含五個部分:
- 領(lǐng)導選舉(Leader election):在集群初始化或者舊領(lǐng)導異常情況下,選舉出一個新的領(lǐng)導。
- 日志復制(Log replication): 當有新的日志寫入時,領(lǐng)導能把它復制到集群中大多數(shù)節(jié)點上。
- 集群成員變更(Cluster Membership changes): 當集群有擴容或者縮容的需求,集群各節(jié)點能準確感知哪些節(jié)點新加入或者被去除。
- 日志壓縮(Log compaction): 當寫入的日志文件越來越大,重啟時節(jié)點回放(replay)日志的時間將無限延長,并且新節(jié)點加入集群時傳送日志文件也會無限拉大。需要定期對日志文件進行重整壓縮。
- 讀寫一致性(Read/write consistency): 客戶端作為集群的外部組件,當一個客戶端寫入新數(shù)據(jù)時,能保證后續(xù)所有客戶端都能讀到最新的值。
數(shù)據(jù)一致性的幾層語義:
- 數(shù)據(jù)的寫入順序要保持一致。否則可能出現(xiàn)很多不預期的情況,比如:舊值覆蓋新值。先刪后增變成先增后刪,數(shù)據(jù)消失了
- 對成功寫入的數(shù)據(jù)供認不諱。如果數(shù)據(jù)被集群表明寫入成功,那么集群各節(jié)點都應該認可并接受這個結(jié)果,而不會出現(xiàn)某些節(jié)點不知情的情況。
- 數(shù)據(jù)寫入成功保證持久化的。如果集群表明數(shù)據(jù)寫入成功,數(shù)據(jù)卻沒落盤。這時宕機了,那么數(shù)據(jù)就丟失了。
Raft在 保序性、共識性、持久性都能很好的支持這就能證明:
在假定領(lǐng)導永不宕機的前提下,Raft是能夠保證集群數(shù)據(jù)一致性的。
Leader在非正常運行情況下,推選出的新Leader至少擁有所有已提交的日志,從而保證數(shù)據(jù)一致性。
因為Raft規(guī)定:一切寫入操作必須由Leader管控,所以選主這段時間客戶端的寫入會被告知失敗或者進行不斷重試。這里其實一定程度上犧牲了集群的可用性來保證一致性。然而就像CAP定理告訴我們的,分布式系統(tǒng)不能既保證一致性C,又保證可用性A。而Raft集群選擇了 C和 P,也就一定程度失去了A。
所以,Raft算法能保證集群的數(shù)據(jù)一致性。
什么是 DLedger
Dledger 的定位
DLedger 就是一個基于 raft 協(xié)議的 commitlog 存儲庫,
Dledger 作為一個輕量級的 Java Library,它的作用就是將 Raft 有關(guān)于算法方面的內(nèi)容全部抽象掉,開發(fā)人員只需要關(guān)心業(yè)務即可也是 RocketMQ 實現(xiàn)新的高可用多副本架構(gòu)的關(guān)鍵。
如上圖所示,Dledger 只做一件事情,就是 CommitLog。Etcd 雖然也實現(xiàn)了 Raft 協(xié)議,但它是自己封裝的一個服務,對外提供的接口全是跟它自己的業(yè)務相關(guān)的。在這種對 Raft 的抽象中,可以簡單理解為有一個 StateMachine 和 CommitLog。CommitLog 是具體的寫入日志、操作記錄,StateMachine 是根據(jù)這些操作記錄構(gòu)建出來的系統(tǒng)的狀態(tài)。在這樣抽象之后,Etcd 對外提供的是自己的 StateMachine 的一些服務。Dledger 的定位就是把上一層的 StateMachine 給去除,只留下 CommitLog。這樣的話,系統(tǒng)就只需要實現(xiàn)一件事:就是把操作日志變得高可用和高可靠。
Dledger 的架構(gòu)
從前面介紹的多副本技術(shù)的演進可以知道,我們要做的主要有兩件事:選舉和復制,對應到上面的架構(gòu)圖中,也就是兩個核心類:DLedgerLeaderElector 和 DLedgerStore,選舉和文件存儲。選出 leader 后,再由 leader 去接收數(shù)據(jù)的寫入,同時同步到其他的 follower,這樣就完成了整個 Raft 的寫入過程。
DLedger 的優(yōu)化
Raft 協(xié)議復制過程可以分為四步,先是發(fā)送消息給 leader,leader 除了本地存儲之外,會把消息復制給 follower,然后等待follower 確認,如果得到多數(shù)節(jié)點確認,該消息就可以被提交,并向客戶端返回發(fā)送成功的確認。
DLedger對于復制過程有以下優(yōu)化:
- 1、對于復制過程,DLedger 采用一個異步線程模型提高吞吐量和性能。
- 2、DLedger 中,leader 向所有 follower 發(fā)送日志也是完全相互獨立和并發(fā)的,leader 為每個 follower 分配一個線程去復制日志 這是一個獨立并發(fā)的復制過程。
- 3、在獨立并發(fā)的復制過程內(nèi),DLedger設計并實現(xiàn)日志并行復制的方案,不再需要等待前一個日志復制完成再復制下一個日志,只需在 follower 中維護一個按照日志索引排序請求列表, follower 線程按照索引順序串行處理這些復制請求。而對于并行復制后可能出現(xiàn)數(shù)據(jù)缺失問題,可以通過少量數(shù)據(jù)重傳解決。
通過以上3點,優(yōu)化這一復制過程。
在可靠性方面DLedger也對網(wǎng)絡分區(qū)做了優(yōu)化,而且也對DLedger 做了可靠性測試:
DLedger對網(wǎng)絡分區(qū)的優(yōu)化
如果出現(xiàn)上圖的網(wǎng)絡分區(qū),n2與集群中的其他節(jié)點發(fā)生了網(wǎng)絡隔離,按照 raft 論文實現(xiàn),n2會一直請求投票,但得不到多數(shù)的投票,term 一直增大。一旦網(wǎng)絡恢復后,n2就會去打斷正在正常復制的n1和n3,進行重新選舉。為了解決這種情況,DLedger 的實現(xiàn)改進了 raft 協(xié)議,請求投票過程分成了多個階段,其中有兩個重要階段:WAIT_TO_REVOTE和WAIT_TO_VOTE_NEXT。WAIT_TO_REVOTE是初始狀態(tài),這個狀態(tài)請求投票時不會增加 term,WAIT_TO_VOTE_NEXT則會在下一輪請求投票開始前增加 term。對于圖中n2情況,當有效的投票數(shù)量沒有達到多數(shù)量時??梢詫⒐?jié)點狀態(tài)設置WAIT_TO_REVOTE,term 就不會增加。通過這個方法,提高了Dledger對網(wǎng)絡分區(qū)的容忍性。
DLedger 可靠性測試
官方不僅測試對稱網(wǎng)絡分區(qū)故障,還測試了其他故障下 Dledger 表現(xiàn)情況,包括隨機殺死節(jié)點,隨機暫停一些節(jié)點的進程模擬慢節(jié)點的狀況,以及 bridge、partition-majorities-ring 等復雜的非對稱網(wǎng)絡分區(qū)。在這些故障下,DLedger 都保證了一致性,驗證了 DLedger 有很好可靠性。
Dledger 的應用
在 RocketMQ 上的應用
RocketMQ 4.5 版本發(fā)布后,可以采用 RocketMQ on DLedger 方式進行部署。DLedger commitlog 代替了原來的 commitlog,使得 commitlog 擁有了選舉復制能力,然后通過角色透傳的方式,raft 角色透傳給外部 broker 角色,leader 對應原來的 master,follower 和 candidate 對應原來的 slave。
因此 RocketMQ 的 broker 擁有了自動故障轉(zhuǎn)移的能力。在一組 broker 中, Master 掛了以后,依靠 DLedger 自動選主能力,會重新選出 leader,然后通過角色透傳變成新的 Master。
Dledger構(gòu)建高可用的嵌入式 KV 存儲
DLedger 還可以構(gòu)建高可用的嵌入式 KV 存儲。
我們沒有一個嵌入式且高可用的解決方案。RocksDB 可以直接用,但是它本身不支持高可用。(Rocks DB 是Facebook開源的單機版數(shù)據(jù)庫)
有了 DLedger 之后,我們把對一些數(shù)據(jù)的操作記錄到 DLedger 中,然后根據(jù)數(shù)據(jù)量或者實際需求,恢復到hashmap 或者 rocksdb 中,從而構(gòu)建一致的、高可用的 KV 存儲系統(tǒng),應用到元信息管理等場景。
參考:
https://developer.aliyun.com/article/713017
https://blog.csdn.net/csdn_lhs/article/details/108029978