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





























