分布式一致性方案有多難?看完這篇我驚出冷汗
兄弟們,今天咱們來聊聊分布式系統(tǒng)里一個(gè)讓人又愛又恨的話題 —— 分布式一致性方案。為啥說又愛又恨呢?愛的是它是分布式系統(tǒng)的核心基石,恨的是它實(shí)在太難搞了,分分鐘讓人驚出冷汗。別著急,咱們慢慢嘮,盡量用大白話,帶點(diǎn)小幽默,讓大家舒舒服服地把這硬骨頭啃下來。
一、先搞懂啥是分布式一致性
咱先不著急上技術(shù),先打個(gè)比方。假設(shè)你和幾個(gè)好兄弟一起開了個(gè)小超市,每個(gè)人負(fù)責(zé)一個(gè)貨架,記錄商品的庫存。突然有一天,你們覺得單干不行,得搞個(gè)聯(lián)合庫存系統(tǒng),大家的庫存數(shù)據(jù)得保持一致,不然顧客來買東西,這邊說有貨,那邊說沒貨,那就鬧笑話了。這時(shí)候,你們就面臨著分布式一致性的問題 —— 多個(gè)節(jié)點(diǎn)(你們各自的貨架記錄)的數(shù)據(jù)要保持一致。
在分布式系統(tǒng)中,一致性指的是多個(gè)副本之間的數(shù)據(jù)是否一致。這里的副本可以是數(shù)據(jù)庫的副本、緩存的副本等等。分布式系統(tǒng)為啥會(huì)有一致性問題呢?因?yàn)樗啥鄠€(gè)節(jié)點(diǎn)組成,節(jié)點(diǎn)之間通過網(wǎng)絡(luò)通信,而網(wǎng)絡(luò)是不可靠的,可能會(huì)出現(xiàn)延遲、丟包,甚至節(jié)點(diǎn)故障等情況。這就好比你和兄弟之間打電話溝通庫存,電話可能會(huì)斷,信號(hào)可能不好,導(dǎo)致信息傳遞有誤。
二、一致性模型:不同的一致性要求
在分布式系統(tǒng)中,根據(jù)一致性的強(qiáng)弱,有幾種不同的一致性模型。
強(qiáng)一致性
強(qiáng)一致性是最嚴(yán)格的一致性模型,要求更新操作完成后,所有節(jié)點(diǎn)在同一時(shí)間看到的最新數(shù)據(jù)都是一致的。就像你和兄弟說,我剛剛把蘋果的庫存從 10 個(gè)改成 8 個(gè),那不管誰去查,都得馬上看到 8 個(gè),不能有的看到 10 個(gè),有的看到 8 個(gè)。這種一致性很好理解,但實(shí)現(xiàn)起來最難,因?yàn)橐幚砀鞣N網(wǎng)絡(luò)問題和節(jié)點(diǎn)故障,確保所有節(jié)點(diǎn)都收到更新并應(yīng)用。
弱一致性
弱一致性就比較寬松了,允許在更新操作后,不是所有節(jié)點(diǎn)都立即看到最新數(shù)據(jù),而是過一段時(shí)間后才會(huì)逐漸一致。比如你改了蘋果庫存,可能有的兄弟節(jié)點(diǎn)過一會(huì)兒才收到消息更新庫存。這種一致性實(shí)現(xiàn)起來簡單一些,但可能會(huì)出現(xiàn)數(shù)據(jù)不一致的情況,比如顧客在不同節(jié)點(diǎn)查詢到不同的庫存數(shù)據(jù)。
最終一致性
最終一致性是弱一致性的一種特殊情況,它保證在沒有新的更新操作的情況下,經(jīng)過一段時(shí)間后,所有節(jié)點(diǎn)的數(shù)據(jù)最終會(huì)達(dá)到一致。這是分布式系統(tǒng)中最常用的一致性模型,比如分布式緩存 Redis 的集群模式就采用了最終一致性。就像你和兄弟雖然一開始庫存數(shù)據(jù)不一樣,但隨著時(shí)間推移,通過各種同步機(jī)制,最終會(huì)變成一樣的。
三、分布式一致性方案大起底
接下來,咱們就來看看那些讓人又愛又恨的分布式一致性方案。
二階段提交(2PC):理想很豐滿,現(xiàn)實(shí)很骨感
二階段提交是分布式事務(wù)中常用的一致性方案,它把事務(wù)的提交過程分成兩個(gè)階段:準(zhǔn)備階段和提交階段。
準(zhǔn)備階段(投票階段)
協(xié)調(diào)者(可以看作是分布式系統(tǒng)中的一個(gè)中心節(jié)點(diǎn))向所有參與者(其他節(jié)點(diǎn))發(fā)送準(zhǔn)備請(qǐng)求,詢問是否可以執(zhí)行事務(wù)提交操作。參與者收到請(qǐng)求后,會(huì)執(zhí)行事務(wù)的所有操作,但不會(huì)真正提交事務(wù),而是記錄日志,然后向協(xié)調(diào)者返回是否同意提交的響應(yīng)。比如在數(shù)據(jù)庫的分布式事務(wù)中,參與者會(huì)執(zhí)行數(shù)據(jù)庫的更新操作,但只是把數(shù)據(jù)寫入日志,不真正更新數(shù)據(jù)庫的數(shù)據(jù)。
這就好比公司要組織一次團(tuán)建,領(lǐng)導(dǎo)(協(xié)調(diào)者)先問各個(gè)部門(參與者),下周六大家有沒有空參加團(tuán)建呀?各個(gè)部門回去看看自己的工作安排,然后告訴領(lǐng)導(dǎo)能不能參加。
提交階段(執(zhí)行階段)
如果協(xié)調(diào)者收到所有參與者都同意提交的響應(yīng),那么就向所有參與者發(fā)送提交請(qǐng)求,參與者收到后就會(huì)真正提交事務(wù)。如果有任何一個(gè)參與者不同意提交,或者在規(guī)定時(shí)間內(nèi)沒有收到參與者的響應(yīng),協(xié)調(diào)者就會(huì)向所有參與者發(fā)送回滾請(qǐng)求,參與者回滾事務(wù)。
接著上面的例子,領(lǐng)導(dǎo)收到所有部門都說有空,那就通知大家下周六去團(tuán)建;要是有一個(gè)部門說沒空,領(lǐng)導(dǎo)就只能取消團(tuán)建,通知大家各忙各的。
看起來挺合理的吧?但在實(shí)際應(yīng)用中,二階段提交有很多問題。首先,它是同步阻塞的,在準(zhǔn)備階段和提交階段,所有參與者都處于阻塞狀態(tài),不能處理其他事務(wù),這會(huì)影響系統(tǒng)的性能。其次,它對(duì)協(xié)調(diào)者的依賴很強(qiáng),如果協(xié)調(diào)者在提交階段發(fā)生故障,比如發(fā)送提交請(qǐng)求到一半死機(jī)了,有的參與者收到了提交請(qǐng)求,有的沒收到,就會(huì)導(dǎo)致數(shù)據(jù)不一致。還有,網(wǎng)絡(luò)延遲和超時(shí)處理也很麻煩,比如參與者在準(zhǔn)備階段同意提交,但在提交階段因?yàn)榫W(wǎng)絡(luò)問題沒收到提交請(qǐng)求,一直處于阻塞狀態(tài),不知道該提交還是回滾。
三階段提交(3PC):想彌補(bǔ) 2PC 的缺陷,卻還是有漏洞
三階段提交是為了改進(jìn)二階段提交的缺陷而提出的,它把提交過程分成了三個(gè)階段:CanCommit、PreCommit 和 DoCommit。
CanCommit 階段
協(xié)調(diào)者向參與者發(fā)送一個(gè)詢問請(qǐng)求,詢問是否可以執(zhí)行事務(wù)。參與者只需要檢查自身的資源是否足夠,比如數(shù)據(jù)庫的連接、鎖等,而不需要執(zhí)行實(shí)際的事務(wù)操作。如果可以,就返回同意;否則返回不同意。這相當(dāng)于領(lǐng)導(dǎo)先問大家,下周六理論上有沒有可能參加團(tuán)建,不考慮具體的工作安排,只是看看時(shí)間上是否有沖突。
PreCommit 階段
如果 CanCommit 階段所有參與者都同意,協(xié)調(diào)者就會(huì)進(jìn)入 PreCommit 階段,向參與者發(fā)送預(yù)提交請(qǐng)求,參與者執(zhí)行事務(wù)操作,但和 2PC 一樣,不真正提交,記錄日志,然后返回確認(rèn)。如果有參與者不同意,或者協(xié)調(diào)者超時(shí),就會(huì)進(jìn)入中斷流程,發(fā)送中斷請(qǐng)求,參與者不執(zhí)行事務(wù)。
這一步和 2PC 的準(zhǔn)備階段有點(diǎn)類似,但這里協(xié)調(diào)者在發(fā)送預(yù)提交請(qǐng)求之前,會(huì)先發(fā)送一個(gè)心跳包,檢查參與者的狀態(tài),避免 2PC 中協(xié)調(diào)者故障導(dǎo)致的問題。
DoCommit 階段
如果 PreCommit 階段所有參與者都確認(rèn),協(xié)調(diào)者就發(fā)送提交請(qǐng)求,參與者真正提交事務(wù);如果有問題,就發(fā)送回滾請(qǐng)求。
三階段提交相比二階段提交,減少了阻塞的時(shí)間,在 CanCommit 階段可以提前發(fā)現(xiàn)一些無法執(zhí)行事務(wù)的情況,避免后續(xù)的無用操作。而且引入了超時(shí)機(jī)制,當(dāng)參與者超時(shí)沒收到協(xié)調(diào)者的請(qǐng)求時(shí),會(huì)自動(dòng)進(jìn)行提交或回滾,一定程度上解決了協(xié)調(diào)者故障的問題。但它還是沒有完全解決分布式系統(tǒng)中的一致性問題,比如在 DoCommit 階段,協(xié)調(diào)者發(fā)送提交請(qǐng)求后故障,部分參與者收到了,部分沒收到,還是會(huì)導(dǎo)致數(shù)據(jù)不一致。而且實(shí)現(xiàn)起來比 2PC 更復(fù)雜,所以在實(shí)際應(yīng)用中也不是特別廣泛。
Paxos 算法:分布式一致性的經(jīng)典之作,卻難倒一片英雄漢
Paxos 算法是分布式一致性領(lǐng)域的經(jīng)典算法,由 Leslie Lamport 提出。它的目標(biāo)是在一個(gè)可能發(fā)生消息丟失、重復(fù)、延遲的分布式系統(tǒng)中,確保多個(gè)進(jìn)程對(duì)某個(gè)值達(dá)成一致。
基本概念
- 提案(Proposal):包含一個(gè)提案編號(hào)和一個(gè)值,提案編號(hào)是唯一的,且單調(diào)遞增。
- 提議者(Proposer):提出提案的節(jié)點(diǎn),負(fù)責(zé)發(fā)起一致性過程。
- 接受者(Acceptor):接收提案的節(jié)點(diǎn),決定是否接受提案。
- 學(xué)習(xí)者(Learner):只需要知道最終達(dá)成一致的值,不參與提案的過程。
算法過程
Paxos 算法的過程可以分為兩個(gè)階段:prepare 階段和 accept 階段。
prepare 階段
提議者選擇一個(gè)提案編號(hào) n,向大多數(shù)接受者發(fā)送 prepare 請(qǐng)求。接受者收到 prepare 請(qǐng)求后,如果提案編號(hào) n 大于之前收到的所有提案編號(hào),就會(huì)返回自己之前接受過的提案中編號(hào)最大的那個(gè)提案的值(如果有的話),并承諾不再接受編號(hào)小于 n 的提案。
這就好比在一個(gè)會(huì)議上,大家要決定選哪個(gè)方案,第一個(gè)人(提議者)說我要提一個(gè)方案,編號(hào)是 1,然后問大部分人(接受者),你們之前有沒有接受過其他方案呀?如果沒有,或者我的編號(hào)比你們之前的都大,你們就告訴我你們之前的情況,并且以后別接受比我編號(hào)小的方案。
accept 階段
提議者收到大多數(shù)接受者的 prepare 響應(yīng)后,就可以確定一個(gè)值。如果有接受者在 prepare 響應(yīng)中返回了之前接受過的提案的值,提議者就把這個(gè)值作為當(dāng)前提案的值;否則,提議者可以自己確定一個(gè)值。然后提議者向這些接受者發(fā)送 accept 請(qǐng)求,包含提案編號(hào) n 和確定的值。接受者收到 accept 請(qǐng)求后,如果提案編號(hào) n 不小于之前承諾的最小提案編號(hào),就接受這個(gè)提案,并記錄下來。
當(dāng)有一個(gè)提案被大多數(shù)接受者接受后,這個(gè)值就被選定了,所有學(xué)習(xí)者就可以學(xué)習(xí)這個(gè)值,達(dá)成一致。
Paxos 算法的正確性證明非常復(fù)雜,需要滿足一系列的條件,比如安全性和活性。安全性保證不會(huì)有錯(cuò)誤的決定,活性保證最終會(huì)達(dá)成一致。但它的實(shí)現(xiàn)也很困難,因?yàn)橐幚砀鞣N異常情況,比如提議者故障、接受者故障、網(wǎng)絡(luò)分區(qū)等。而且 Paxos 算法的描述比較抽象,很多人第一次看都覺得云里霧里,這也是它難倒眾多開發(fā)者的原因。不過,基于 Paxos 算法衍生出了很多實(shí)用的方案,比如 Raft 算法。
Raft 算法:更易理解和實(shí)現(xiàn)的分布式一致性算法
Raft 算法是為了讓分布式一致性算法更易理解和實(shí)現(xiàn)而設(shè)計(jì)的,它把分布式系統(tǒng)中的節(jié)點(diǎn)狀態(tài)分為三種:領(lǐng)導(dǎo)者(Leader)、跟隨者(Follower)和候選人(Candidate)。
領(lǐng)導(dǎo)者選舉
Raft 算法中,首先需要選舉出一個(gè)領(lǐng)導(dǎo)者,領(lǐng)導(dǎo)者負(fù)責(zé)處理客戶端的請(qǐng)求,管理日志的復(fù)制,保證數(shù)據(jù)的一致性。當(dāng)跟隨者在一段時(shí)間內(nèi)沒有收到領(lǐng)導(dǎo)者的心跳包(心跳包用于維持領(lǐng)導(dǎo)者的地位),就會(huì)認(rèn)為領(lǐng)導(dǎo)者故障,進(jìn)入候選人狀態(tài),發(fā)起選舉。候選人向其他節(jié)點(diǎn)發(fā)送請(qǐng)求投票,當(dāng)獲得大多數(shù)節(jié)點(diǎn)的投票后,就成為新的領(lǐng)導(dǎo)者。
這就像一個(gè)班級(jí)選班長,一開始有一個(gè)班長(領(lǐng)導(dǎo)者),如果同學(xué)們(跟隨者)很久沒收到班長的消息,就覺得班長可能不干了,然后有人(候選人)站出來說我來當(dāng)班長,大家投票,得票最多的就成為新班長。
日志復(fù)制
客戶端的寫請(qǐng)求由領(lǐng)導(dǎo)者處理,領(lǐng)導(dǎo)者收到請(qǐng)求后,會(huì)將請(qǐng)求作為日志條目添加到自己的日志中,然后發(fā)送給所有跟隨者。跟隨者收到日志條目后,會(huì)將其寫入自己的日志,并返回確認(rèn)。當(dāng)領(lǐng)導(dǎo)者收到大多數(shù)跟隨者的確認(rèn)后,就會(huì)提交該日志條目,并通知跟隨者提交,這樣所有節(jié)點(diǎn)的數(shù)據(jù)就保持一致了。
對(duì)于讀請(qǐng)求,通??梢灾苯佑深I(lǐng)導(dǎo)者處理,或者如果跟隨者保存了最新的數(shù)據(jù),也可以處理讀請(qǐng)求,但需要確保跟隨者的數(shù)據(jù)是最新的,這可以通過領(lǐng)導(dǎo)者的心跳包來保證。
Raft 算法相比 Paxos 算法,更容易理解和實(shí)現(xiàn),因?yàn)樗褑栴}分解成了領(lǐng)導(dǎo)者選舉、日志復(fù)制等幾個(gè)相對(duì)獨(dú)立的部分,每個(gè)部分都有明確的狀態(tài)和流程。而且它的安全性和活性也有很好的保證,在實(shí)際應(yīng)用中被廣泛采用,比如分布式存儲(chǔ)系統(tǒng) etcd 就采用了 Raft 算法。
其他一致性方案
除了上面提到的這些方案,還有一些其他的一致性方案,比如 ZAB 協(xié)議(ZooKeeper 原子廣播協(xié)議),它和 Raft 算法類似,也是通過領(lǐng)導(dǎo)者選舉和日志復(fù)制來保證一致性,主要用于 ZooKeeper 分布式協(xié)調(diào)服務(wù)中。還有基于時(shí)間戳的向量時(shí)鐘算法,用于解決分布式系統(tǒng)中的因果一致性問題,通過給每個(gè)操作分配一個(gè)時(shí)間戳向量,來判斷操作之間的因果關(guān)系,從而保證一致性。
四、分布式一致性方案的難點(diǎn)在哪里
說了這么多方案,咱們來總結(jié)一下分布式一致性方案的難點(diǎn)到底有哪些。
網(wǎng)絡(luò)的不可靠性
這是分布式系統(tǒng)面臨的最根本問題之一。網(wǎng)絡(luò)可能會(huì)出現(xiàn)延遲、丟包、分區(qū)等情況,導(dǎo)致節(jié)點(diǎn)之間的通信失敗。比如在二階段提交中,協(xié)調(diào)者發(fā)送的提交請(qǐng)求可能因?yàn)榫W(wǎng)絡(luò)分區(qū),只有部分參與者收到,導(dǎo)致數(shù)據(jù)不一致。在 Paxos 和 Raft 算法中,都需要處理網(wǎng)絡(luò)分區(qū)的情況,確保在網(wǎng)絡(luò)恢復(fù)后,系統(tǒng)能重新達(dá)成一致。
節(jié)點(diǎn)的故障
節(jié)點(diǎn)可能會(huì)因?yàn)橛布收?、軟件崩潰等原因而失效。?dāng)領(lǐng)導(dǎo)者節(jié)點(diǎn)故障時(shí),需要快速選舉出新的領(lǐng)導(dǎo)者,并且保證日志的一致性。在 Raft 算法中,領(lǐng)導(dǎo)者選舉的時(shí)間和日志復(fù)制的機(jī)制都需要考慮節(jié)點(diǎn)故障的情況,確保系統(tǒng)的可用性和一致性。
一致性和可用性的權(quán)衡
在分布式系統(tǒng)中,有一個(gè)著名的 CAP 定理,它指出一個(gè)分布式系統(tǒng)不可能同時(shí)滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯(cuò)性(Partition Tolerance)這三個(gè)特性,最多只能同時(shí)滿足兩個(gè)。這就意味著我們?cè)谠O(shè)計(jì)分布式一致性方案時(shí),需要根據(jù)實(shí)際需求,在一致性和可用性之間做出權(quán)衡。比如在金融系統(tǒng)中,可能更注重一致性,而在一些高可用性的系統(tǒng)中,可能會(huì)選擇最終一致性,犧牲一定的強(qiáng)一致性來保證系統(tǒng)的可用性。
實(shí)現(xiàn)的復(fù)雜性
從上面介紹的各種方案可以看出,分布式一致性方案的實(shí)現(xiàn)都非常復(fù)雜,需要處理各種異常情況,保證算法的正確性和效率。比如 Paxos 算法的正確性證明需要嚴(yán)格的數(shù)學(xué)推導(dǎo),Raft 算法雖然更易理解,但實(shí)現(xiàn)起來也需要處理領(lǐng)導(dǎo)者選舉、日志復(fù)制、節(jié)點(diǎn)故障恢復(fù)等多個(gè)模塊,每個(gè)模塊都可能出現(xiàn)問題,需要仔細(xì)調(diào)試和測試。
五、如何選擇合適的分布式一致性方案
說了這么多難點(diǎn),那我們?cè)趯?shí)際項(xiàng)目中該如何選擇合適的分布式一致性方案呢?
明確業(yè)務(wù)需求
首先要明確業(yè)務(wù)對(duì)一致性的要求。如果是金融交易、訂單支付等場景,要求強(qiáng)一致性,那就需要選擇像二階段提交、Paxos 算法、Raft 算法等能夠保證強(qiáng)一致性的方案,但也要考慮系統(tǒng)的性能和可用性。如果是一些對(duì)一致性要求不高的場景,比如用戶行為日志記錄、緩存數(shù)據(jù)同步等,可以選擇最終一致性的方案,比如分布式緩存的異步同步機(jī)制。
考慮系統(tǒng)的規(guī)模和復(fù)雜度
如果是小規(guī)模的分布式系統(tǒng),節(jié)點(diǎn)數(shù)量較少,業(yè)務(wù)邏輯簡單,可以選擇實(shí)現(xiàn)相對(duì)簡單的方案,比如 Raft 算法,或者一些基于開源框架的解決方案,比如 etcd 提供的 Raft 實(shí)現(xiàn),減少自己開發(fā)的難度和風(fēng)險(xiǎn)。如果是大規(guī)模的分布式系統(tǒng),節(jié)點(diǎn)數(shù)量眾多,業(yè)務(wù)復(fù)雜,可能需要結(jié)合多種方案,比如在分布式事務(wù)中使用二階段提交,在分布式存儲(chǔ)中使用 Paxos 或 Raft 算法,同時(shí)還要考慮系統(tǒng)的擴(kuò)展性和容錯(cuò)性。
參考開源項(xiàng)目和最佳實(shí)踐
開源項(xiàng)目是我們學(xué)習(xí)和借鑒的寶貴資源。比如 ZooKeeper 使用 ZAB 協(xié)議,etcd 使用 Raft 算法,Redis 的集群模式采用最終一致性,我們可以研究這些開源項(xiàng)目的實(shí)現(xiàn),了解它們?cè)诓煌瑘鼍跋碌膽?yīng)用和優(yōu)化策略。同時(shí),行業(yè)內(nèi)的最佳實(shí)踐也很重要,比如在微服務(wù)架構(gòu)中,如何保證多個(gè)服務(wù)之間的數(shù)據(jù)一致性,通常會(huì)采用分布式事務(wù)、最終一致性等方案,結(jié)合業(yè)務(wù)的特點(diǎn)進(jìn)行選擇。
六、總結(jié):分布式一致性,難,但值得挑戰(zhàn)
分布式一致性方案確實(shí)很難,它涉及到網(wǎng)絡(luò)、節(jié)點(diǎn)故障、算法設(shè)計(jì)、性能優(yōu)化等多個(gè)方面,每一個(gè)環(huán)節(jié)都可能讓人頭疼不已。但它又是分布式系統(tǒng)的核心,是我們構(gòu)建高可靠、高可用分布式系統(tǒng)的基礎(chǔ)。
從二階段提交到三階段提交,從 Paxos 算法到 Raft 算法,每一個(gè)方案的出現(xiàn)都是為了解決之前方案的不足,都是無數(shù)開發(fā)者智慧的結(jié)晶。雖然實(shí)現(xiàn)起來復(fù)雜,但隨著技術(shù)的發(fā)展,越來越多的開源框架和工具提供了成熟的一致性解決方案,讓我們?cè)趯?shí)際項(xiàng)目中可以站在巨人的肩膀上,不用從頭開始造輪子。
作為 Java 開發(fā)者,我們需要了解這些分布式一致性方案的原理和適用場景,根據(jù)業(yè)務(wù)需求做出合適的選擇。同時(shí),也要不斷學(xué)習(xí)和研究新的技術(shù),迎接分布式系統(tǒng)帶來的挑戰(zhàn)。也許現(xiàn)在覺得難,但只要慢慢啃,總有一天會(huì)豁然開朗。