一、分佈式事務的簡介
分佈式事務對於一致性的要求是強一致性,因此對於我們後續討論有一定的借鑑意義。如:bob給smith轉賬,強一致性的要求一定是需要對外來說bob減錢的同時smith加錢。
圖1:單機環境下的一致性 |
圖2:分佈式環境下的一致性 |
單機環境見圖1:bob的減錢和smith的加錢都轉同一個庫來做,可以採用數據庫的事務特性輕鬆支持。保證bob給smith轉賬的安全性。而分佈式環境見圖2:假設應用服務器是A,bob端的數據庫是B,smith端的數據是C,那麼A做成一個轉賬,需要B事務成功提交,並且C事務成功提交。然而因爲網絡的影響,可能出現兩種情況
1. 如果bob扣款成功,而網絡通知smith失敗了,則會出現bob的錢減了,smith的錢沒加
2. 如果bob扣款不成功,而smith加錢成功了,則會出現smith錢增加了,但是bob的錢也沒減少
二、分佈式事務的6個協議
(2PC、3PC、XTS、TCC、查詢補償、消息事務與消息重試)
1、2PC
這種不一致的問題困擾着大家。任意一邊出錯想要回滾另一邊都不是簡單的數據庫回滾的事情( 因爲此時已經成功提交),而是需要做業務的逆向操作,而不同業務的逆操作都不同,導致複雜性增加。考慮數據庫事務的執行實際上是先將執行操作寫入binlog,等到最後通過一個commit指令將binlog的內容一次更新到表中,或者寫到一半通過一個rollback指令將binlog中的內容回滾。於是乎,可以想到使用2個階段來執行這個過程,第一階段,寫入binlog;第二階段執行commit或者rollback。這就是著名的兩階段提交協議(2PC)。如果仔細考慮,會發現兩階段協議並沒有解決問題,只不過降低了出錯的概率而已,因爲第二階段同樣存在上面的兩種情況。注意最終狀態是多臺機器的狀態&&的 結果。上圖是兩階段協議的時序圖。
1. 考慮prepare階段的響應(因爲請求階段和執行階段都可以在最後響應中體現出來),對於分佈式環境中,任意時刻考慮3種狀態:成功、失敗、超時。 2. 考慮commit階段,同樣考慮成功失敗超時3種狀態。 |
2、3PC
1. 考慮cancommit階段的響應。 a.成功。不必處理,執行後續行爲precommit。 b.失敗。說明無法執行,無須後續提交或回滾行爲。 c.超時。保守處理,超時可以當做失敗。
2. 考慮precommit階段的響應。 a.成功。不必處理,執行後續行爲docommit。 b.失敗。執行階段出錯,執行後續行爲rollback。 c.超時。執行階段太慢,也可能是網絡階段太慢或丟包,但是保守處理,超時可以當做出錯。
3. 考慮cancommit階段的響應。 a.成功。整個事務成功執行。 b.失敗。提交出錯,假設此時前面的B已經提交成功了,則同樣面臨無法回滾的問題。 c.超時。保守處理,超時可以當做失敗。 例外情況,即自cancommit返回成功後的任意階段A掛掉了,那麼BC同樣能夠知道這個事務正在發生(因爲cancommit已經提交了足夠信息讓BC知曉此事),於是BC可以在無A的情況下繼續執行後續的階段(比如BC投票啓動新的A’,並提供A’足夠信息)。於是3PC正好解決了2PC的例外情況。 但是3PC仍然存在類似2PC的問題,即最後階段失敗或超時同樣有可能出現數據不一致的問題。所以3PC仍然只是降低了發生概率,並沒有真正解決問題。 |
3、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在工業界的應用,是相當了不起的設計,通過各種方式規避了各種可能的不一致性,在性能,效率等方面做到了平衡。
4、TCC(Try/Confirm/Cancel)
業務補償類型,其基本思想是對每一個業務操作做一個逆操作,一旦成功了,就做正向業務,一旦失敗了就做業務的逆操作。通常在業務邏輯簡單並且正逆操作清晰的時候用比較好。
5、查詢補償
典型的場景是向銀行發送了轉賬請求未得到明確的成功失敗返回碼,此時先做業務結果的查詢,根據結果做相應處理,比如查詢結果成功,則置狀態爲成功,查詢結果失敗,則做相應的業務補償,查詢結果爲未知,則繼續查詢。
6、消息事務及消息重試
事務消息及消息重試本質上都是將一些通用的事務交給消息中間件,通過消息中間件來保證消息的最終一致性。
事實上,消息事務解決了這類問題,即本地事務和消息應當有一致性,解決這個一致性比較麻煩,比如消息中間件和業務同時實現XA;或者採用一些更加複雜的方式,比如將消息表與業務表放同庫,利用數據庫的事務來保證一致性,而消息系統只需要輪訓該消息表即可;當然,也有消息的二階段提交+補償的方式。消息事務解決了消息發起方,即生產者與消息中間件之間的一致性問題。
try {
//數據庫操作;消息投遞
} catch(Exception e) {
//回滾
}
消息中間件與消費者之間的一致性問題則需要通過重試+冪等來解決。消息重試中主要考慮重試次數以及重試時間的閾值變化。