上一章我們了解了zookeeper到底是什么,這一章重點來看zookeeper當初到底面臨什么問題?而zookeeper又是如何解決這些問題的?
實際上zookeeper主要就是解決分布式環境下的一致性問題。那么解決這個問題到底有哪些難點呢?我們一步一步來闡述和推理這個過程。
分布式事務
我們首先考慮一致性的特殊情況,即分布式事務的情況。分布式事務對於一致性的要求是強一致性,因此對於我們后續討論有一定的借鑒意義。這里我們用到一個經典的例子:bob給smith轉賬,強一致性的要求一定是需要對外來說bob減錢的同時smith加錢。見文獻1(圖片也來源於文獻1)
單機環境下是這樣的
簡單講就是有關bob的減錢和smith的加錢都轉同一個庫來做,可以采用數據庫的事務特性輕松支持。保證bob給smith轉賬的安全性。
而分布式環境就變這樣了
假設應用服務器是A,bob端的數據庫是B,smith端的數據是C,那么A做成一個轉賬,需要B事務成功提交,並且C事務成功提交。然而因為網絡的影響,可能出現兩種情況
1. 如果bob扣款成功,而網絡通知smith失敗了,則會出現bob的錢減了,smith的錢沒加
2. 如果bob扣款不成功,而smith加錢成功了,則會出現smith錢增加了,但是bob的錢也沒減少
2PC
這種不一致的問題困擾着大家。任意一邊出錯想要回滾另一邊都不是簡單的數據庫回滾的事情( 因為此時已經成功提交),而是需要做業務的逆向操作,而不同業務的逆操作都不同,導致復雜性增加。考慮數據庫事務的執行實際上是先將執行操作寫入binlog,等到最后通過一個commit指令將binlog的內容一次更新到表中,或者寫到一半通過一個rollback指令將binlog中的內容回滾。於是乎,可以想到使用2個階段來執行這個過程,第一階段,寫入binlog;第二階段執行commit或者rollback。這就是著名的兩階段提交協議(2PC)。如果仔細考慮,會發現兩階段協議並沒有解決問題,只不過降低了出錯的概率而已,因為第二階段同樣存在上面的兩種情況。注意最終狀態是多台機器的狀態&&的 結果。以下是兩階段協議的時序圖:
1. 考慮prepare階段的響應(因為請求階段和執行階段都可以在最后響應中體現出來),對於分布式環境中,任意時刻考慮3種狀態:成功、失敗、超時。
- a.成功。不必處理,執行后續行為commit。
- b.失敗。這是執行階段出錯,執行后續行為rollback。
- c.超時。這可能是執行階段太慢,也可能是網絡階段太慢或丟包,但是保守處理,超時可以當做出錯。
可以看出,prepare階段的問題能夠完全避免。
2. 考慮commit階段,同樣考慮成功失敗超時3種狀態。
- a. 成功。整個事務成功執行
- b. 失敗。提交出錯,假設此時前面的B已經提交成功了,則同樣面臨需要回滾B卻無法回滾的問題,因為B已經提交成功了。
- c. 超時。同上。
還有一種例外情況,即prepare階段完成后A掛了,則B,C即進入不知所措的狀態。
可以看出,在2PC中事務無法做到像單機一樣安全,只不過降低了出問題的概率。
3PC
針對如何解決2PC中的例外情況,出現了3階段提交協議。3階段的主要改進是把2階段的prepare再分為canCommit和preCommit兩個階段。
1. 考慮cancommit階段的響應。
- a.成功。不必處理,執行后續行為precommit。
- b.失敗。說明無法執行,無須后續提交或回滾行為。
- c.超時。保守處理,超時可以當做失敗。
2. 考慮precommit階段的響應。
- a.成功。不必處理,執行后續行為docommit。
- b.失敗。執行階段出錯,執行后續行為rollback。
- c.超時。執行階段太慢,也可能是網絡階段太慢或丟包,但是保守處理,超時可以當做出錯。
3. 考慮cancommit階段的響應。
- a.成功。整個事務成功執行。
- b.失敗。提交出錯,假設此時前面的B已經提交成功了,則同樣面臨無法回滾的問題。
- c.超時。保守處理,超時可以當做失敗。
XTS
工業界的對分布式事務的應用是如何呢?可以參考某寶的知名分布式框架XTS。
XTS本質上是2PC(實際上如果引入3PC會多2n次網絡交互,在量大時反而更加不安全)。XTS引入協調者A的server部分,實際上是一個大集群,以配置的方式接入各種需要分布式事務的業務,集群由專門的團隊維護,保證其可用性和性能;而協調者A的client部分則通過發起方調用,prepare階段時,先通過client將本次事務信息發送到server,落庫,然后即時推送prepare請求到B和C,當收到B,C的響應時把他們狀態入庫,如果正常,則做commit提交;否則會用定時任務去推送未完成的狀態直到完成。上文提到的prepare之后協調者A掛了這種情況,在server集群的保證下,幾乎很少會發生。而上文提到的所有超時的情況,都可以通過定時任務推送拿到一個確定的狀態而不是盲目的選擇回滾或者提交。另外由於B和C都是集群,很少會發生多次請求過去無響應的情況。直到最后一種情況就是commit時B成功了C失敗了,或者反過來B失敗C成功,這種情況成為懸掛事務,最終等待人工來解決,據說每天都有幾筆到幾十筆。
無疑XTS作為2PC在工業界的應用,是相當了不起的設計,通過各種方式規避了各種可能的不一致性,在性能,效率等方面做到了平衡。
TCC(Try/Confirm/Cancel)
業務補償類型,其基本思想是對每一個業務操作做一個逆操作,一旦成功了,就做正向業務,一旦失敗了就做業務的逆操作。通常在業務邏輯簡單並且正逆操作清晰的時候用比較好。
查詢補償
典型的場景是向銀行發送了轉賬請求未得到明確的成功失敗返回碼,此時先做業務結果的查詢,根據結果做相應處理,比如查詢結果成功,則置狀態為成功,查詢結果失敗,則做相應的業務補償,查詢結果為未知,則繼續查詢。
消息事務及消息重試
事務消息及消息重試本質上都是將一些通用的事務交給消息中間件,通過消息中間件來保證消息的最終一致性。
事實上,消息事務解決了這類問題,即本地事務和消息應當有一致性,解決這個一致性比較麻煩,比如消息中間件和業務同時實現XA;或者采用一些更加復雜的方式,比如將消息表與業務表放同庫,利用數據庫的事務來保證一致性,而消息系統只需要輪訓該消息表即可;當然,也有消息的二階段提交+補償的方式。消息事務解決了消息發起方,即生產者與消息中間件之間的一致性問題。
try{ //數據庫操作 //消息投遞 }catch(Exception e){ //回滾 }
消息中間件與消費者之間的一致性問題則需要通過重試+冪等來解決。消息重試中主要考慮重試次數以及重試時間的閾值變化。