詳談分佈式最終一致性 原

“什麼是分佈式系統?這取決於看系統的角度。對於坐在鍵盤前使用IBM個人電腦的人來說,電腦不是一個分佈式的系統。但對於在電腦主板上趴着的蟲子來說,這臺電腦就是一個分佈式系統。” —— Leslie Lamport

 

引言

分佈式一致性問題隨處可見,任何一個實體/聯接模型,都可能存在分佈式一致性問題。如果把單機拆開來看,CPU、內存、I/O設備組成的機箱本身就是一個小型的分佈式系統,需要確保對這個系統操作的最終一致性。幸運的是這部分工作已經交給操作系統和數據庫軟件來幫我們完成。而在大型分佈式企業級應用中,分佈式最終一致性方案需要根據系統自身特點量身定製,是系統設計的重點。近年來隨着滬江業務的快速增長和微服務治理推廣,本地ACID事務早已不能滿足業務和系統的發展需求。大部分業務流程都需要跨多微服務的調用來協作完成,並且要求系統確保分佈式最終一致性。

可以選擇分佈式事務框架方案,目前主流的分佈式事務框架大致可分爲3類實現 :

  • 基於XA協議的兩階段提交(2PC)方案

  • 基於支付寶最早提出的TCC(Try、Confirm、Cancel)方案

  • 基於ebay最早提出的消息隊列異步確保方案

此外還有較輕的解決方案,業務系統可以根據自身需要,選擇通過冪等/重試、狀態機、恢復日誌、異步校驗等技術來確保最終一致性。

 

重型武器

採用分佈式事務框架的方案,最終一致性由分佈式事務框架保證,業務程序員對框架細節完全透明。選擇這種方案,需要注意幾個點。首先,由於分階段提交協議本身的脆弱性,主流分階段提交協議如2PC,3PC, TCC都無法完全確保最終一致性,要採用異步校驗的手段兜底。其次,分階段提交協議帶來的高延遲,多次協議通信RTT帶來的時間損耗。第三,基於消息隊列異步確保的分佈式事務框架實現,需要考慮消息可靠性和業務侵入問題。分佈式事務框架也有巨大的優勢,首先,分佈式事務被框架封裝成切面,業務開發只需關心純業務。其次,分佈式事務的代碼開發量大大減少。對一致性和代碼質量有極高要求的銀行、金融領域,分佈式事務框架是最佳選擇。

 

輕型武器

不同於採用分佈式事務框架的最終一致性方案,程序員也可以選擇通過冪等/重試、狀態機、恢復日誌、異步校驗等技術來確保最終一致性。這種方案不受限於平臺和框架,系統較精簡靈活,初期業務系統大都基於這種分佈式一致性解決方案。不過這種方案對業務開發的要求更高,分佈式一致性邏輯要業務程序員代碼實現,容易出現bug。

其實,主流的分佈式事務框架也是通過這些基本的系統機制如冪等/重試、狀態機、恢復日誌、異步校驗等來確保的最終一致性,對比兩種方案,下文主要圍繞後一種展開論述,討論5點使系統達成分佈式最終一致性的技術實踐。

 

原則

 

1、CAP定理

如下三屬性,任何一個聯網的共享數據系統最多隻能同時滿足 2 個 :

  • 一致性(Consistency) : 每次讀取都會收到最新的寫入或錯誤

  • 可用性(Availability) : 每個請求都會收到一個不是錯誤的響應

  • 分區容忍性(Partition tolerance) : 節點之間的網絡丟棄(或延遲)了任意數量的消息,系統仍繼續運行

由於分區容忍性是可伸縮的最基本要求,放棄分區容忍性等於放棄可伸縮,所以分區容忍性是必選項,大部分的分佈式系統都是在C和A之間做選擇。需要注意的是,雖然C都叫一致性,但CAP定理中的C和數據庫事務ACID中的C是完全不同的兩個定義。

 

2、BASE原則

  • Basically Available : 基本可用

  • Soft state : 軟狀態

  • Eventual consistency : 最終一致性

BASE原則發展自CAP定理,捨棄了系統的強一致性而選擇AP,但每個應用可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性。用較通俗的話來描述就是 : “過程寬鬆,結果嚴格,你的老闆不關心過程,只看結果”。NoSQL數據庫Cassandra就是遵循的BASE原則設計。不過也有分佈式系統設計不是遵循BASE原則,而是選擇CAP中的CP,如HBase。當然,系統對CAP三者的取捨並不是一成不變,可以根據實際需要改變策略。

 

實踐

 

1、重試

 

 

重試機制可以使分佈式不一致數據自動恢復,前提是重試接口要提供冪等保證。重試機制是達成分佈式最終一致性的重要手段。例如,超時重傳是TCP協議保證數據可靠性的一個重要機制,核心思想其實就是重試。在此我向大家推薦一個架構學習交流裙。交流學習裙號:687810532,裏面會分享一些資深架構師錄製的視頻錄像

  • 同步重試 : 在上次請求失敗或超時,程序再次發起同步調用請求。後端程序不推薦同步重試,其一因爲同步等待佔用系統線程資源,其二因爲重試引起的流量放大,可能導致系統雪崩。

  • 異步重試 : 通過異步系統(消息隊列或調度中間件)對失敗或超時請求再次發起調用。推薦這種方式的重試,重試的時間間隔可以設置爲根據重試次數指數增長,超過重試閾值仍未成功,可以報警通知並由人工訂正。

重試也是提高系統可用性的一種有效手段。如果一個服務的可用性爲98%(有1個9),1次重試之後其可用性可達到99.96%(3個9),2次重試可以達到99.9992%(5個9)。

 

2、冪等

冪等的數學定義爲

用通俗的話來說就是 : 相同的操作執行多次 和 執行一次產生的效果是一樣的。有的操作是天然冪等的,如查詢、刪除操作。有的操作是人爲使其冪等,例如TCP的超時重傳操作就是冪等的,無論客戶端將一個seq字節傳送多少次,服務端窗口只會用一次該字節。冪等實現方式有很多 :

  • 基於記錄的悲觀鎖,MySQL中通過SELECT FOR UPDATE語句實現。這種實現方式要設置AUTOCOMMIT=0,加鎖和更新記錄在同一個事務中,長時間鎖定記錄會降低系統的TPS,高併發場景不推薦使用。

  • 基於記錄版本號或狀態機的樂觀鎖方案,適用於更新數據場景。例如,用戶下單購買一個商品的扣庫存操作實現冪等,可以用如下SQL語句實現 : UPDATE stocktable SET stock = stock - 1, version = version + 1 WHERE product_id = 123 and version = 1

  • 基於數據庫唯一索引的去重表,適用於插入和更新數據的場景,由數據庫惟一索引確保多次插入和更新操作只有一次生效。

  • 基於全局唯一標識token實現,這種方案要注意幾點 : 1、這裏校驗token是否可使用 和 置token爲已使用,是一個CAS原子操作,需要確保在一個原子操作中。 2、如果token存儲使用的是Redis,那麼驗證token的CAS操作可以使用原子自增操作incr,如果Redis值大於1則token不可使用,反之可使用。還有一種實現方式是token生成系統將token預先寫入Redis,用刪除操作來校驗token是否被使用,刪除成功代表token未被使用可執行操作。 3、如果token存儲使用的是MySQL,根據token分庫分表和建惟一索引,同時通過insert語句來判斷token是否存在,如果insert失敗則token不可使用,反之可使用。

 

3、狀態機

狀態機是表示實體的狀態根據條件轉移的數學模型。通過狀態機模型,系統可以判斷當前不一致狀態,以及如何校正不一致狀態到一致狀態。這樣說可能比較抽象,我們拿發微信羣紅包的例子來說明。當你點開發紅包按鈕,輸入總金額、紅包個數、標題,點擊支付成功後。其實根據時間先後紅包系統後臺至少經歷過這樣一個狀態機 :

  • 1、當輸入總金額、紅包個數、標題點擊提交,首先後臺創建一個初始化狀態(INIT)紅包

  • 2、接着系統將根據你輸入的總金額和個數n將紅包拆分成n分,此時紅包的狀態爲拆分成功(SPLITTED)

  • 3、此時紅包後臺會監聽異步支付消息,如果支付成功則將紅包置爲支付成功(PAID)

  • 4、之後紅包系統會通知微信IM系統,發送消息通知羣裏的用戶,此時紅包狀態爲(NOTIFIED)

  • 5.1、羣裏的用戶把紅包搶光了,紅包狀態被系統置爲已搶光(RUNOUT)

  • 5.2、還有一種可能,如果羣裏都是程序員,忙着擼代碼,沒時間搶紅包,一定時間後紅包自動退款到支付賬號,紅包狀態便爲(REFUND)

這只是一個正常業務流程的紅包狀態機,異常情況如拆分失敗、支付失敗、通知失敗、退款失敗等情況也同理存在一個狀態機器。爲了方便業務實體狀態回滾和校正,狀態機要儘量設計精簡,轉移到下一個狀態的邊儘可能的只有一條路徑(終結狀態會例外),這樣在回滾和校正時能夠明確前一個狀態和後一個狀態。舉個例子,如果系統發現紅包一直處於PAID狀態,而並沒有流轉到NOTIFIED狀態,能夠判斷是通知羣用戶出現異常,可以根據實際情況重新通知羣用戶或者將超期紅包退款。

 

4、恢復日誌

恢復日誌是程序現場的記錄,也是業務數據恢復的重要依據。恢復日誌log要求全局唯一的requestId來標示請求(實際的業務場景可採用不會重複有含義的業務id),出現異常,可以根據requestId維度redo和undo業務操作,恢復日誌具體可分爲三部分 :

  • requestId請求開始時,記錄REQUEST START requestId

  • 本地修改時,記錄全部的(requestId,x,originalValue, destValue)四元組,x代表操作對象,修改前x的值爲originalValue,本次修改的目的操作值爲destValue

  • requestId結束時,記錄REQUEST End requestId

恢復日誌是系統從不一致的狀態恢復到一致狀態的重要數據,丟失恢復日誌,意味着不一致可能無法恢復。爲什麼是可能,因爲有時可以通過狀態機對不一致的狀態進行恢復。

 

5、異步校驗

徹底解決分佈式一致性問題,有著名的Paxos算法,通過該算法分佈式系統自發達成一致性。而在具體的業務場景,完全不需要系統自發的達成共識,我們只要在業務系統外部加上嚴格的業務約束,用來仲裁業務系統的狀態。通過異步校驗,可以發現分佈式系統中的異常狀態,並通過恢復日誌進行腳本批量恢復或者人工處理恢復,根據校驗的粒度有 :

  • 根據業務實體id校驗,使用消息隊列,將需要校驗業務id投遞給校驗系統,進行異步校驗。

  • 根據時間維度批量校驗,使用異步調度框架,根據時間粒度批量獲取進行異步校驗。

此外,並不是所有系統都有可靠消息隊列和調度服務支撐,業務系統可以增加一個本地業務id校驗回執字段,校驗系統根據校驗步驟回調設置校驗回執字段,並對校驗未通過的數據進行重校驗或者訂正。

 

總結

分佈式最終一致性問題,後端程序員在實際開發中經常遇到。在實際系統開發中爲了確保最終一致性,往往需要組合多個技術點打出組合拳,因爲招數是死的,程序員是活的。總結上面提到的技術點,我們可以通過冪等和重試機制,使得不一致數據能夠自動恢復;通過異步校驗機制發現業務系統的不一致數據;通過狀態機和恢復日誌,糾正不一致的業務數據。最後,感謝閱讀本文,歡迎留言討論。在此我向大家推薦一個架構學習交流裙。交流學習裙號:687810532,裏面會分享一些資深架構師錄製的視頻錄像

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章