天天看點

李豔鵬:分布式一緻性協定

國際開放标準組織Open Group定義了DTS(分布式事務處理模型),模型中包含4個角色:應用程式、事務管理器、資料總管、通信資料總管四部分。事務處理器是統管全局的管理者,資源處理器和通信資源處理器是事務的參與者。

J2EE規範也包含此分布式事務處理模型的規範,并在所有的AppServer中進行實作,J2EE規範中定義了TX協定和XA協定,TX協定定義應用程式與事務管理器之間的接口,而XA協定定義了事務管理器與資源處理器之間的接口,在過去,大家使用AppServer,例如:Websphere、Weblogic、Jboss等配置資料源的時候會看見類似XADatasource的資料源,這就是實作了DTS的關系型資料庫的資料源。企業級開發JEE中,關系型資料庫、JMS服務扮演資料總管的角色,而EJB容器則扮演事務管理器的角色。

下面我們就介紹兩階段送出協定、三階段送出協定以及阿裡巴巴提出的TCC,它們都是根據DTS這一思想演變出來的。

上面描述的JEE的XA協定就是根據兩階段送出來保證事務的完整性,并實作分布式服務化的強一緻性。

兩階段送出協定把分布式事務分成兩個過程,一個是準備階段,一個是送出階段,準備階段和送出階段都是由事務管理器發起的,為了接下來講解友善,我們把事務管理器稱為協調者,把資管管理器稱為參與者。

兩階段如下:

準備階段:協調者向參與者發起指令,參與者評估自己的狀态,如果參與者評估指令可以完成,參與者會寫redo或者undo日志(這也是前面提起的Write-Ahead Log的一種),然後鎖定資源,執行操作,但是并不送出

送出階段:如果每個參與者明确傳回準備成功,也就是預留資源和執行操作成功,協調者向參與者發起送出指令,參與者送出資源變更的事務,釋放鎖定的資源;如果任何一個參與者明确傳回準備失敗,也就是預留資源或者執行操作失敗,協調者向參與者發起中止指令,參與者取消已經變更的事務,執行undo日志,釋放鎖定的資源

兩階段送出協定成功場景示意圖如下:

李豔鵬:分布式一緻性協定

我們看到兩階段送出協定在準備階段鎖定資源,是一個重量級的操作,并能保證強一緻性,但是實作起來複雜、成本較高,不夠靈活,更重要的是它有如下緻命的問題:

阻塞:從上面的描述來看,對于任何一次指令必須收到明确的響應,才會繼續做下一步,否則處于阻塞狀态,占用的資源被一直鎖定,不會被釋放

單點故障:如果協調者當機,參與者沒有了協調者指揮,會一直阻塞,盡管可以通過選舉新的協調者替代原有協調者,但是如果之前協調者在發送一個送出指令後當機,而送出指令僅僅被一個參與者接受,并且參與者接收後也當機,新上任的協調者無法處理這種情況

腦裂:協調者發送送出指令,有的參與者接收到執行了事務,有的參與者沒有接收到事務,就沒有執行事務,多個參與者之間是不一緻的

上面所有的這些問題,都是需要人工幹預處理,沒有自動化的解決方案,是以兩階段送出協定在正常情況下能保證系統的強一緻性,但是在出現異常情況下,目前處理的操作處于錯誤狀态,需要管理者人工幹預解決,是以可用性不夠好,這也符合CAP協定的一緻性和可用性不能兼得的原理。

三階段送出協定是兩階段送出協定的改進版本。它通過逾時機制解決了阻塞的問題,并且把兩個階段增加為三個階段:

詢問階段:協調者詢問參與者是否可以完成指令,協調者隻需要回答是還是不是,而不需要做真正的操作,這個階段逾時導緻中止

準備階段:如果在詢問階段所有的參與者都傳回可以執行操作,協調者向參與者發送預執行請求,然後參與者寫redo和undo日志,執行操作,但是不送出操作;如果在詢問階段任何參與者傳回不能執行操作的結果,則協調者向參與者發送中止請求,這裡的邏輯與兩階段送出協定的的準備階段是相似的,這個階段逾時導緻成功

送出階段:如果每個參與者在準備階段傳回準備成功,也就是預留資源和執行操作成功,協調者向參與者發起送出指令,參與者送出資源變更的事務,釋放鎖定的資源;如果任何一個參與者傳回準備失敗,也就是預留資源或者執行操作失敗,協調者向參與者發起中止指令,參與者取消已經變更的事務,執行undo日志,釋放鎖定的資源,這裡的邏輯與兩階段送出協定的送出階段一緻

三階段送出協定成功場景示意圖如下:

然而,這裡與兩階段送出協定有兩個主要的不同:

增加了一個詢問階段,詢問階段可以確定盡可能早的發現無法執行操作而需要中止的行為,但是它并不能發現所有的這種行為,隻會減少這種情況的發生

在準備階段以後,協調者和參與者執行的任務中都增加了逾時,一旦逾時,協調者和參與者都繼續送出事務,預設為成功,這也是根據機率統計上逾時後預設成功的正确性最大

三階段送出協定與兩階段送出協定相比,具有如上的優點,但是一旦發生逾時,系統仍然會發生不一緻,隻不過這種情況很少見罷了,好處就是至少不會阻塞和永遠鎖定資源。

上面兩節講解了兩階段送出協定和三階段送出協定,實際上他們能解決案例2-轉賬和案例3-下訂單和扣庫存中的分布式事務的問題,但是遇到極端情況,系統會發生阻塞或者不一緻的問題,需要營運或者技術人工解決。無論兩階段還是三階段方案中都包含多個參與者、多個階段實作一個事務,實作複雜,性能也是一個很大的問題,是以,在網際網路高并發系統中,鮮有使用兩階段送出和三階段送出協定的場景。

阿裡巴巴提出了新的TCC協定,TCC協定将一個任務拆分成Try、Confirm、Cancel,正常的流程會先執行Try,如果執行沒有問題,再執行Confirm,如果執行過程中出了問題,則執行操作的逆操Cancel,從正常的流程上講,這仍然是一個兩階段的送出協定,但是,在執行出現問題的時候,有一定的自我修複能力,如果任何一個參與者出現了問題,協調者通過執行操作的逆操作來取消之前的操作,達到最終的一緻狀态。

可以看出,從時序上,如果遇到極端情況下TCC會有很多問題的,例如,如果在Cancel的時候一些參與者收到指令,而一些參與者沒有收到指令,整個系統仍然是不一緻的,這種複雜的情況,系統首先會通過補償的方式,嘗試自動修複的,如果系統無法修複,必須由人工參與解決。

從TCC的邏輯上看,可以說TCC是簡化版的三階段送出協定,解決了兩階段送出協定的阻塞問題,但是沒有解決極端情況下會出現不一緻和腦裂的問題。然而,TCC通過自動化補償手段,會把需要人工處理的不一緻情況降到到最少,也是一種非常有用的解決方案,根據線人,阿裡在内部的一些中間件上實作了TCC模式。

我們給出一個使用TCC的實際案例,在秒殺的場景,使用者發起下單請求,應用層先查詢庫存,确認商品庫存還有餘量,則鎖定庫存,此時訂單狀态為待支付,然後指引使用者去支付,由于某種原因使用者支付失敗,或者支付逾時,系統會自動将鎖定的庫存解鎖供其他使用者秒殺。

TCC協定使用場景示意圖如下:

李豔鵬:分布式一緻性協定

總結一下,兩階段送出協定、三階段送出協定、TCC協定都能保證分布式事務的一緻性,他們保證的分布式系統的一緻性從強到弱,TCC達到的目标是最終一緻性,其中任何一種方法都可以不同程度的解決案例2:轉賬、案例3:下訂單和扣庫存的問題,隻是實作的一緻性的級别不一樣而已,對于案例4:同步逾時可以通過TCC的理念解決,如果同步調用逾時,調用方可以使用fastfail政策,傳回調用方的使用方失敗的結果,同時調用服務的逆向cancel操作,保證服務的最終一緻性。

原文釋出時間為:2017-12-6

本文作者:李豔鵬