雪花台湾

又出現異常數據?架構師深度剖析分佈式系統「事務」

關於作者:張帆(Zachary)。堅持用心打磨每一篇高質量原創。

本文首發於公衆號:「跨界架構師」(ID:Zachary_ZF)。

一、爲什麼需要事務


如果說「共識」解決的是「水平」問題,那麼「事務」解決的是「垂直」問題。是如何讓一條繩上的螞蚱共同起舞?

事務只是一個計算機術語,而事務的體現形式其實在我們生活中也無處不在。任何我們認爲應該是這樣的事情,去確保它達到預期的過程就是「事務」。 往小了說,我們平時在走路的時候,向前擺動左手的同時擡右腿,如果不是這樣的話就是不一致,別人會說你走路不協調。所以我們小時候父母會通過各種方式教會我們這個,這些各式各樣的方式就好比我們在軟件開發中去實施「事務」一樣,一題是多解的。

二、事務的來源


提到事務不得不提到「XA規範」[1],這是分佈式還沒大行其道的時期,被大多數的數據庫作爲其內部分佈式事務實現的接口標準。



▲圖片來源於論文中,版權歸原作者所有

「XA規範」就是上圖中「RM」和「TM」的交互規範和接口定義。僅僅是定義了xa_和ax_系列的函數原型以及功能描述、約束和實施規範等,並不包括建議的實現方式。後面會提到的兩階段提交(2PC)是「TM」協調「RM」們完成事務的方法。

所以其實可以說,事務起源於數據庫,輝煌於分佈式系統。在摩爾定律還適用的時候,軟件系統爲了承載更大的流量或者說用戶數,開始運用「分治」的思想來設計。然後隨着互聯網的蓬勃發展,B/S應用大行其道的背景下,分佈式系統越來越常見。並且隨着一個個巨無霸互聯網公司的出現,越來越被鼓吹和傳頌。

一輪明月的背後是一個陰暗面,從來不讓人看見。


能被吹捧的永遠是有益的一面,再加上耀眼的數據:多少TPS、多少QPS,更抓人眼球。但是這背後爲了讓「分治」後的系統能夠儘可能的像單個個體一樣運作,各類專家學者們通過多年研究,纔有瞭如今的各種著名理論和解決方案。

三、分佈式系統中的事務問題


正如前面所說,事務問題其實一直存在,只是在分佈式系統中被放大了。並且隨着系統拆分的粒度越細,問題的複雜度成指數上升。

分佈式系統的事務,不得不提到被廣爲流傳的兩個理論:「CAP」、「BASE」。

「CAP」理論由Eric Brewer在2000年PODC會議上提出[2],所以還被稱爲Brewer定理。是Eric Brewer在Inktomi期間研發搜索引擎、分佈式web緩存時得出的一個猜想:

It is impossible for a web service to provide the three following guarantees : Consistency, Availability and Partition-tolerance.


後來Seth Gilbert和Nancy Lynch對其進行了證明[3],成爲我們熟知的「CAP」定理(感謝園友@bangerlee的信息收集)。

對,就是下面這張經典的圖。



▲圖片來源於網絡,版權歸原作者所有



舉個不是特別嚴謹的例子,這就好比要實現一個系統不能產生BUG(C),並且10天內完成上線(A),以及需要多人團隊一起協作進行(P)。我們做開發的也很清楚這三者是無法兼得的。況且只要是一個組織,團隊協作是無法避免的,正如這裏的分區容忍性一樣,比如得考慮人員請假的問題。剩下的2項,如果說可以達到沒有BUG的話,那就是時間無限延長,但也只是無限趨近於0,並不能達到真正的0,因爲沒有人可以保證發現了所有的BUG。

「BASE」理論是由時任ebay架構師的Dan Pritchett提出的[4],本質上就是對「線性一致性」的弱化。

「BASE」理論解釋如下:


「BASE」理論的提出並不是取代「CAP」理論,讓我們在實際的工作中就可以完全的撇開「線性一致性」。並不是這樣,而是引導我們可以區分核心和非核心,然後分別對待,核心部分還是需要用CAP理論來保證「線性一致性」。爲什麼要區別對待?根本上還是無法容忍「線性一致性」帶來的巨大的性能損耗,因爲它是反可伸縮性的。但是隻要涉及到Money之類的高敏感數據的操作部分,還是必須保證「線性一致性」。

還是上面的例子,我們側重於降低核心功能的BUG,不花過多精力在非核心功能上(BA)。我們允許產生不影響核心功能的BUG(S),但是必須最終要修復(E)。

四、分佈式事務的解決方案


如果說「CAP」理論和「BASE」理論是「道」,那麼圍繞這兩個理論演化的解決方案就是「術」。對我們來說,在實際的運用中根據所處的場景找到最合適的,是我們最重要的事。

以「CAP」爲基礎的強一致性解決方案都會引入一個類似“協調器”的東西來作爲全局事務的掌控者,可以來看一下。

01兩階段提交(2PC)[5]

▲圖片來源於網絡,版權歸原作者所有

印象中左耳朵耗子(陳皓)之前拿西方結婚時的儀式做過一個形象的比喻。大致好像是牧師分別詢問男女雙方“你願意嗎?”相當於這裏的「請求提交」,得到的“yes”相當於「是」這個答覆。然後再要求給對方帶上戒指,這個要求就相當於這裏的「提交」,帶完戒指之後的反饋就是「ACK」。

另外值得注意的是,參與者在答覆「是」之前會將自己的內部資源變爲阻塞狀態。因此如果在產生阻塞後協調者出問題,那麼這些被阻塞的資源有可能就一直不被釋放了,需要額外的介入。

2PC相對來說是最簡單的事務模型,但缺點也更多。其它缺點諸如:在某些場景下的數據不一致(參與者與協調者共同與「提交」環節掛了)、阻塞範圍過大等問題。

02 三階段提交(3PC)[6]

▲圖片來源於網絡,版權歸原作者所有

3PC的出現就是通過增加複雜度(性能也因此降低)來解決或優化2PC中的一部分問題。本質的變化就是在2PC的「請求提交」之後增加了一個「準備提交」環節,以增加每個參與者需要等待其它的參與者確認後方可進行具體的操作。

03 TCC

在國內,由於阿里的光環加持下TCC好像更火,風頭蓋過了2PC和3PC。其本質上是另闢蹊徑達到了和3PC類似的效果。

▲圖片來源於網絡,版權歸原作者所有

這個概念最初是由Pat Helland於2007年提出的[7],那時還叫「Tentative-Confirmation-Cancellation」,在2008年的軟件開發2.0技術大會上支付寶CTO(程立)將其在國內推廣開來。

以上這三種就是主流的DTS(Distributed Transaction Service)框架。值得一提的是,不管是3PC還是TCC,只要涉及到故障恢復或者重試機制,那麼「冪等性」問題必須要提上來了。比如3PC中「提交」階段某個參與者和協調者同時掛了,但是這個參與者在掛之前已經做了commit操作。那麼故障恢復後其實沒人知道它是否執行過了commit,協調者只會爲了能100%確保commit指令被送達,又會發起一次commit通知,這時候如果沒有做好「冪等性」就會發生重複commit的問題。

下面聊聊以「BASE」理論爲基礎的解決方案。

01 異步消息——本地消息表

▲點擊圖片可查看大圖

這種實現方式的思路,源於ebay,與提出BASE理論在同一篇論文中[4]。設計思想是將遠程分佈式事務拆分成一系列的本地事務,藉助關係型數據庫中的表即可實現。

02 異步消息——不支持事務的MQ

▲點擊圖片可查看大圖

其實大部分的MQ都是不支持事務的,所以我們需要自己想辦法解決可能出現的MQ消息未能成功投遞出去的問題。有個便宜可以撿的是,如果需投遞的MQ消息條數僅有1的話,可以將本地事務的commit放於消息投遞之後即可避免此問題。僞代碼如下:

try{

beginTrans();

modifyLocalData1();

modifyLocalData2();

deliverMessageToMQ();

commitTrans();

}catch(Exception ex){

rollbackTrans();

}


03 異步消息——支持事務的MQ

▲點擊圖片可查看大圖

據我所知,目前唯一支持事務的MQ框架是RockerMQ,並且於近期纔開源了事務部分實現,《RocketMQ 4.3正式發佈,支持分佈式事務》(http://www.infoq.com/cn/news/2018/08/rocketmq-4.3-release)。這樣的確能省很多事~,直接放一張阿里方面給出的圖感受一下實現細節。

▲圖片來源於網絡,版權歸原作者所有(點擊圖片可查看大圖)

不過其實有一個疑點我沒有去驗證,有知道的小夥伴們可以留言下,就是RocketMQ是否有防止consumer(上圖中的訂閱方)在消費完成後發送的ACK丟失的機制。如果能達到這點,對於consumer內部的方法冪等性需求就低了很多。

04 Saga



▲點擊圖片可查看大圖

Saga是1987年就提出的概念[8],核心是:

Saga原則上是個鏈式的「長活事務」,整個處理耗時可能會很長。所以可以通過增加save point(保存點,類似於遊戲裏的存檔),便於故障恢復和提速,如向前恢復(重試)和向後恢復(回滾)。不過,也可以並行多個子事務,但一般在運用中心節點的Saga模式中,如圖。



▲點擊圖片可查看大圖

只是在我們打破了鏈式規則後必須要額外確保執行了「回滾」之後再接收到「正向請求」,等於“請求無效”的效果。中心節點模式還有一個比較大的好處是能夠更好的避免事務之間的循環依賴關係。

05 Gossip協議

額外提一下,這個其實是一個具體的、運用BASE理論實現的協議,藉由Cassandra的熱火而讓更多人知道了。這協議一般會用於數據複製、P2P拓撲構造、故障探測等。

看這些案例我們可以發現,基於「CAP」的解決方案都是在線的,而「Base」是允許離線的。好比前者是,累倒了必須得馬上爬起來繼續幹貨,要不然就是失敗。而後者是,慢慢來,只要最終能幹完。

不管怎樣,如果每個解決方案中增加「重試」和「回滾」會大大提升程序的自我修正能力,以降低需要人爲介入的比例。識別是否需要人爲介入的方式就是類似於「對賬」的機制,這個機制就是兜底的。最後還需要做一道選擇題來防止混亂:確保參與者的接口符合「冪等性」,或者在中間件裏做到「正好一次(Exactly-once)」。

這些基於「BASE」的解決方案都是可以作爲「CAP」解決方案出現問題時的PlanB來用的,起到補充作用。當然,如非必要,可以優先考慮基於「BASE」的方案,畢竟這纔是天然易伸縮的,自然也能帶來更好的性能。

五、結語


解決方案如此多,所以不管我們是架構師、還是在成爲架構師的路上,甚至在日常生活中,都需要養成Balance的習慣,找到那個最適合的方案

最後還有一招終極大法 —— 減少冗餘。

是亦彼也,彼亦是也,彼亦一是非,此亦一是非。 ——莊子


「事物都具有兩面性」,所以,在選擇走向分佈式之前,慎重考慮下是否有必要,以免給自己徒增麻煩

相关文章