分佈式事務實戰方案彙總

分佈式事務:

1 CAP 定理

1.1 概念

CAP 理論在分佈式系統中

  • 一致性:分佈式環境下多個節點的數據是否強一致
  • 可用性:分佈式服務能一直保證可用狀態。當用戶發出一個請求後,服務能在有限時間內返回結果
  • 分區容忍性:特指對網絡分區的容忍性

對於共享數據系統,最多隻能同時擁有CAP其中的兩個,沒法三者兼顧。

  • 任兩者的組合都有其適用場景
  • 真實系統應當是ACID與BASE的混合體
  • 不同類型的業務可以也應當區別對待
  • 其中,分區容忍性又是不可或缺的。
 
image

結論:分佈式系統中,最重要的是滿足業務需求,而不是追求抽象、絕對的系統特性。

2.2 中間件實例

  • 默認優先選擇AP,弱化C
    Cassandra、Dynamo 等

  • 默認優先選擇CP,弱化A
    HBase、MongoDB 等

2 BASE 理論

核心思想

基本可用(BasicallyAvailable)

分佈式系統在出現故障時,允許損失部分的可用性來保證核心可用。

軟狀態(SoftState)

允許分佈式系統存在中間狀態,該中間狀態不會影響到系統的整體可用性。

最終一致性(EventualConsistency)

分佈式系統中的所有副本數據經過一定時間後,最終能夠達到一致的狀態

一致性模型數據的一致性模型可以分成以下 3 類:

  • 強一致性
    數據更新成功後,任意時刻所有副本中的數據都是一致的,一般採用同步方式實現
  • 弱一致性
    數據更新成功後,系統不承諾立即可以讀到最新寫入的值,也不承諾具體多久後可讀到
  • 最終一致性:弱一致性的一種形式,數據更新成功後,系統不承諾立即可以返回最新寫入的值,但是保證最終會返回上一次更新操作的值。

分佈式系統數據的強一致性、弱一致性和最終一致性可以通過Quorum NRW算法分析。

只要聊到做了分佈式系統,必問分佈式事務,若你對分佈式事務一無所知的話,確實很坑,起碼得知道有哪些方案,一般怎麼來做,每個方案的優缺點是什麼。

現在面試,分佈式系統成了標配,而分佈式系統帶來的分佈式事務也成了標配.
你做系統肯定要用事務,那你用事務的話,分佈式系統之後肯定要用分佈式事務.
先不說你搞過沒有,起碼你得明白有哪幾種方案,每種方案可能有啥坑?比如TCC方案的網絡問題、XA方案的一致性問題

  • 單塊系統裏的事務


     
    image
  • 分佈式系統裏的事務


     
    image

分佈式事務的幾種解決方案

● 異步校對數據的方式
支付寶、微信支付主動查詢支付狀態、對賬單的形式;
● 基於可靠消息(MQ)的解決方案
異步場景;通用性較強;拓展性較高
● TCC編程式解決方案
嚴選、阿里、螞蟻金服自己封裝的DTX

3 XA方案

即兩階段提交事務方案。
需要數據庫廠商支持; JAVA組件有atomikos等。

3.1 程序理解

有一個事務管理器,負責協調多個數據庫(資源管理器)的事務

  1. 事務管理器先問各個數據庫預提交 ok 嗎?
  2. 如果每個數據庫都回復ok,即預提交成功,就正式提交事務,在各個數據庫開始執行操作,這裏失敗會有失敗異常重試,日誌分析,人工重試。

3.3 適用場景

較適合單塊應用中,跨多庫的分佈式事務,而且因爲嚴重依賴於數據庫層面來搞定複雜的事務,效率很低,不適合高併發場景。

如果要玩,那麼基於Spring + JTA就可以搞定。

互聯網公司基本都不用,因爲某個系統內部如果出現跨多庫的操作,是不合規的!現在的微服務,一個大的系統分成幾十甚至上百個服務。一般規約每個服務只能操作自己對應的一個數據庫。

如果你要操作別的服務對應的庫,不允許直連別的服務的庫。
要操作別人的服務的庫,必須通過調用別的服務的接口

4 TCC方案

4.1 三階段

  • Try
    對各個服務的資源做檢測,對資源進行提前鎖定或者預留
  • Confirm
    在各個服務中執行實際的操作
  • Cancel
    如果任何一個服務的業務方法執行出錯,那麼這裏就需要進行補償,即執行已操作成功的業務邏輯的回滾操作

4.2 跨行轉賬案例

涉及到兩個銀行的分佈式事務,如果用TCC方案來實現,思路是這樣的:

  • Try階段
    先把兩個銀行賬戶中的資金給它凍結住,不讓操作了
  • Confirm階段
    執行實際的轉賬操作,A銀行賬戶的資金扣減,B銀行賬戶的資金增加
  • Cancel階段
    如果任何一個銀行的操作執行失敗,那麼就需要回滾進行補償
    比如A銀行賬戶如果已經扣減了,但是B銀行賬戶資金增加失敗了,那麼就得把A銀行賬戶資金給加回去

該方案說實話幾乎很少使用,但也有使用場景.
因爲這個事務的回滾實際上嚴重依賴於你自己寫代碼來回滾和補償了,會造成補償代碼巨大,非常噁心!

比如說我們,一般來說和錢相關的支付、交易等相關的場景,我們會用TCC,嚴格嚴格保證分佈式事務要麼全部成功,要麼全部自動回滾,嚴格保證資金的正確性!

4.3 適用場景

除非你是真的一致性要求太高,是系統中核心之核心的場景!
常見的就是資金類的場景,那可以用TCC方案,自己編寫大量的業務邏輯,自己判斷一個事務中的各個環節是否ok,不ok就執行補償/回滾代碼

而且最好是你的各個業務執行的時間都比較短

但是說實話,一般儘量別這麼搞,自己手寫回滾邏輯,或者是補償邏輯,實在太噁心了,業務代碼也很難維護

4.4 方案示意圖

 
image

5 本地消息表

ebay搞出來的這麼一套思想

5.1 簡介

  • A系統在本地一個事務裏操作的同時,插入一條數據到消息表
  • 接着A系統將這個消息發送到MQ
  • B系統接收到消息後,在一個事務裏,往自己本地消息表裏插入一條數據,同時執行其他的業務操作,如果這個消息已經被處理過了,那麼此時這個事務會回滾,這樣保證不會重複處理消息
  • B系統執行成功後,就會更新自己本地消息表的狀態以及A系統消息表的狀態
  • 如果B系統處理失敗,那麼就不會更新消息表狀態,那麼此時A系統會定時掃描自己的消息表,如果有未處理的消息,會再次發送到MQ中去,讓B再處理

5.2 優點

這個方案保證了最終一致性
哪怕B事務失敗了,但是A會不斷重發消息,直到B那邊成功爲止

5.3 缺陷

最大的問題就在於嚴重依賴於數據庫的消息表來管理事務,這個會導致高併發場景無力,難以擴展呢,一般確實很少用

  • 本地消息表方案


     
    image

6 可靠消息最終一致性方案

乾脆不用本地的消息表了,直接基於MQ來實現事務。比如阿里的RocketMQ就支持消息事務!

6.1 簡介

  • A系統先發送一個prepared消息到MQ,如果這個prepared消息發送失敗,那麼就直接取消操作,不執行了
  • 如果這個消息發送成功過了,那麼接着執行本地事務,如果成功就告訴MQ發送確認消息,如果失敗就告訴MQ回滾消息
  • 如果發送了確認消息,那麼此時B系統會接收到確認消息,然後執行本地的事務
  • MQ會自動定時輪詢所有prepared消息回調你的接口,問你這個消息是不是本地事務處理失敗了,所有沒發送確認的消息,是繼續重試還是回滾?
    這裏你就可以查下數據庫看之前本地事務是否執行,如果回滾了,那麼這裏也回滾吧。這個就是避免可能本地事務執行成功了,別確認消息發送失敗了。
  • 如果系統B的事務失敗了咋辦?
    重試咯,自動不斷重試直到成功,如果實在是不行,要麼就是針對重要的資金類業務進行回滾,比如B系統本地回滾後,想辦法通知系統A也回滾;或者是發送報警由人工來手工回滾和補償

這個還是比較合適的,目前國內互聯網公司大都是這麼玩的,要不你舉用RocketMQ支持的,要不你就自己基於類似ActiveMQ?RabbitMQ?自己封裝一套類似的邏輯出來,總之思路就是這樣子的

  •  

     

    可靠消息最終一致性方案
     
    image

7 最大努力通知方案

7.1 簡介

  • 系統A本地事務執行完後,發送一個消息到MQ

  • 有一專門消費MQ的最大努力通知服務,會消費MQ,然後寫入數據庫中記錄下來,亦可是放入內存隊列,接着調用系統B的接口

  • 若系統B執行成功就ok;若系統B執行失敗,那麼最大努力通知服務就定時嘗試重新調用系統B,反覆N次,最後還是不行才放棄

  •  

     

    最大努力通知方案示意圖
     
     


實戰方案:

1. 最終一致性

1.1 本地事務表 + 輪詢補償

交互流程

 
 
  • commit DB事務提交階段 本地客戶端向DB進行事務提交,此時需要將業務數據和記錄消息事務狀態的信息表同時實現本地事務,此時標記消息事務狀態爲UN_SEND未發送或未完成狀態,此時MQ未發送
  • ack DB確認階段 返回DB事務提交成功或失敗狀態
  • commit MQ事務提交階段 客戶端發起MQ發送請求
  • update 本地事務表更新階段 根據MQ發送結果進行本地消息事務表狀態更新,成功則更新爲SEND發送成功或發送完畢
  • MQ補償 本地消息事務表定時輪詢 對未發送成功消息事務進行補償發送,實現分佈式事務的最終一致

場景:重構業務新老系統雙寫庫同步

 
 

項目背景

這是一個重構系統新老系統同時服役切量遷移的業務場景,老系統正在線上服役爲各業務方提供接口查詢功能,新系統重構完成後需要對接接入,調用流量要陸續從老系統切換到新系統,最終老系統迭代下線。

分佈式事務

需要解決的分佈式事務問題就是,雙系統的數據是異構、分散的,線上不可停量,需要陸續切換完成,因此需要事先將老庫存量數據洗入新庫,此過程中增量數據寫入是存在的,但是最終新老庫是相對一致和統一的,該場景需要解決的是數據雙庫的雙寫問題

設計方案

 
 

場景Q&A

  • Q:如何保證雙庫雙寫? A: 同步寫辛庫,MQ異步寫老庫 本地事務 + 消息事務表
    業務數據持久化開啓數據庫本地事務,該事務中記錄業務數據和同步狀態信息 確保本地事務一定成功,不保證異步MQ事務
    數據庫事務成功後再發送寫老庫的MQ,保證本地事務一定完成纔會觸發MQ發送,這樣確保本地事務一定成功,MQ可能成功也可能失敗 重試MQ事務狀態,最終一致
    如果MQ事務失敗,通過定時任務輪詢進行重發驅動,最終一致
  • Q:異步MQ寫,延遲問題如何解決? A: 增加冗餘查詢
    增加冗餘對另一庫的冗餘查詢進行Double Check。由於調用方几乎不可能同時使用新老系統做業務,因此延遲時間取決於MQ異步消費寫的速度,如果場景比較複雜要確保絕對一致可以增加該處理方式
  • Q:雙寫過程中多個MQ如何保證順序、防重等問題? A: 業務時間 + 業務ID (1)同一個業務ID代表一個同一筆業務,可以依此進行業務防重處理 消息業務ID業務時間消費時間處理邏輯IDTT執行IDTT+1防重不處理 (2)同一個業務ID的基礎上增加業務時間,可以依此保證業務數據的實時刷新 業務時間、消費時間同序 消息業務ID業務時間消費時間處理邏輯IDTT執行IDT+1T+1執行(覆蓋) 業務時間、消費時間亂序 消息業務ID業務時間消費時間處理邏輯IDTT+1拋棄(業務時間較早)IDT+1T執行 (3)不同業務ID的基礎上增加業務時間,可以依此保證不同業務數據的按照業務時間刷新 消息業務ID業務時間消費時間處理邏輯IDT+1T+1執行(業務時間較新)ID+XTT拋棄(業務時間較早)

場景:第三方認證覈驗

 
 

項目背景

這是一個認證系統以來外部覈驗系統進行用戶身份鑑權的場景,即認證系統記錄認證發起記錄,並請求到外部的核驗系統發起一筆覈驗請求,用戶在覈驗系統確認後覈驗結果返回到認證系統確認用戶的真實數據狀態。

分佈式事務

該流程中認證系統是一個本地系統,存放用戶發起的認證流水信息和核驗狀態,依賴外部覈驗系統返回該筆認證記錄的核驗狀態,由於覈驗過程是異步的,用戶可以選擇任意時間完成或者永遠不完成,需要保證每次認證流程只有一筆業務發起,而且需要根據業務時間進行覈驗流程的超時進行強制取消或者補償查詢對齊覈驗狀態等,需要解決的分佈式事務是認證流水、覈驗結果的一致性

設計方案

 
 
  • 正向流程
 
 
  • 補償流程
 
 

場景Q&A

  • Q:本地事務認證流水成功了,外部覈驗系統提交失敗了怎麼辦? A:通過定時任務補償觸發二次提交,只要外部事物提交一直處於未成功,便一直會被重試提交,直到成功
  • Q:外部覈驗系統事務完成了,本地事務認證流水提前被作廢了怎麼辦? A:以本地事務認證流水的結果爲準。本地事務是通過定時任務進行補充提交+外部事務狀態覈驗查詢的,即時在臨界點外部事務完成了,但是超過了業務處理時間已經關閉,不會再補充修改,這也是根據業務場景做的取捨,用戶可以再次發起新流程進行覈驗

1.2 本地事務表 + 事務消息

交互流程

 
 
  • prepare 準備階段 本地客戶端向DB、MQ發送prepare請求
  • ack 準備確認階段 DB、MQ作爲事務參與者返回本地客戶端ack確認
  • commit/rollback 提交/回滾階段 根據事務參與者在準備確認階段返回結果進行事務提交或回滾,此時一旦有事務參與者返回異常或超時未返回則進行回滾提交
  • callback 補償回調階段 當事務超時提交時,則由MQ進行回調查詢數據庫本地事務情況
  • commit/rollback 提交/回滾階段 根據補償回調階段進行事務提交和回滾,實現分佈式事務的最終一致性

場景:分庫分表路由字段綁定

 
 

項目背景

該業務是在分庫分表場景下,需要一個切分鍵字段進行數據分片路由,一般我們ToC業務的話會使用userId這樣的字段進行切分。然而水平切分數據帶來了數據庫讀寫性能的同時也存在一個問題,那就是查詢必須攜帶切分鍵纔可以完成,因爲要依賴它進行數據路由查詢,比如在以userId進行數據路由切分時,如果想按照用戶的身份證、姓名等進行匹配查詢是無法實現的,因爲我們事先是不知道userId、身份證、姓名的等值匹配關係。一般而言,我們可以通過HBase + ES的方案進行解決,即監聽不同庫的binlog日誌,將其按照時間進行排序切分匯入HBase表中,加入要檢索的索引到ES中解決分庫分表下數據切片產生的聚合問題。

分佈式事務

基於以上場景,除了通過HBase+ES實現之外,還可以通過切分鍵與非切分鍵進行數據綁定解決,但是由於切分鍵與非切分鍵的路由一般不一致,數據會分散到不同庫,因此無法做本地事務,這是我們需要解決的分佈式事務問題。

設計方案

 
 
  • 交互流程
 
 

場景Q&A

  • Q:異步MQ寫有延遲,查不到切分鍵如何處理? A:事務處理開始階段通過Redis記錄事務開啓的分佈式鎖標識,當其他存在衝突的同業務在該事務的處理過程有查詢時,通過判斷分佈式鎖標識是否存在來判斷事務的開啓,若存在則異步等待併發起間隔查詢直到事務超時,若事務超時週期內反覆查詢到則返回,否則根據事務處理後結果返回
  • Q:本地事務與MQ事務是如何協作的? A:相當於兩個2PC事務協作。 1.一階段DB、MQ同時prepare(並行) 2.二階段DB先commit,成功後MQ再commit(串行) 流程階段操作Ack反饋持久化是否結束分佈式事務成功正向流程PrepareDB prepare MQ prepareYesNoNoNo正向流程CommitDB commit MQ commitYesYesYesYes------異常流程PrepareDB 或 MQ 異常NoNoYesNo異常流程CommitDB提交異常NoNoYesNo異常流程CommitMQ提交超時 回調本地事務狀態,本地事務成功則提交MQ事務YesYesYesYes異常流程CommitMQ提交超時 回調本地事務狀態,本地事務失敗則取消MQ事務YesNoYesNo
  • Q:會不會存在MQ事務成功,本地事務失敗? A:不會,而且要避免這種情況發生。兩個2PC事務的prepare準備階段可以同時發起,但在commit提交階段要先保證本地數據庫事務成功之後再進行MQ事務消息的commit,也就是在commit階段是存在依賴關係、串行化之行的
  • Q:事務消息如何確保最終一致? A:prepare階段失敗、本地事務commit階段則均不會持久化;當prepare階段成功、本地事務commit成功,此時MQ的commit階段異常,則依賴MQ事務消息的超時commit機制觸發回調本地事務狀態接口方法來決定MQ的二階段是commit還是cancel

1.3 TCC(Try-Commit-Cancel)

交互流程

 
 

TCC事務其實主要包含兩個階段:Try階段、Confirm/Cancel階段。

  • try-嘗試執行業務
    完成所有業務檢查(一致性)預留必須業務資源(準隔離性)
  • confirm-確認執行業務
    真正執行業務不作任何業務檢查只使用Try階段預留的業務資源Confirm操作必須保證冪等性
  • cancel-取消執行業務
    釋放Try階段預留的業務資源Cancel操作必須保證冪等性

從TCC的邏輯模型上我們可以看到,TCC的核心思想是,Try階段檢查並預留資源,確保在Confirm階段有資源可用,這樣可以最大程度的確保Confirm階段能夠執行成功。這裏的資源可以是DB,或者MQ,RPC,只要是分佈式環境中的事務載體就可以,而且需要這些分佈式事務的參與者具備正逆向條件,比如DB、MQ的事務可以支持2PC,可以根據協調者對其進行事務提交或者取消操作,RPC比如賬戶服務可以支持正向增加也可以支持逆向減少,除此之外,DB、MQ要自身支持事務的ACID,這是參與分佈式事務的原子性保證,RPC底層也是DB,這裏可以等同理解。以上參與者具備參與分佈式事務的基本條件後便可以進行整合和使用,業務流程的驅動和事務的完整性由中間協調者來操作和推進。

場景:積分商品兌換

 
 

項目背景

這是一個電商系統比較經典的下單、扣款、發貨流程,在下單之前會通過一系列商品在架狀態、庫存數量、購買限制等有效性過濾,然後在業務系統中進行一個商品兌換的場景。

分佈式事務

由於是商品兌換,對於用戶和系統本身來說是這個過程一個原子性的、一鍵完成的操作,因此下單+動賬+發貨是一個現實存在的分佈式事務。這裏假設訂單數據和動賬數據在一個本地數據庫事務中,持久化開啓數據庫本地事務,該事務中記錄訂單生成數據和動賬數據信息,以及發貨狀態的信息記錄。這裏需要解決的分佈式事務是訂單數據、動賬數據、發貨狀態三種的最終一致

設計方案

 
 
  • 正向流程
 
 
  • 補償流程
 
 

場景Q&A

  • Q:動賬扣款成功,但是發貨失敗怎麼辦? A: 定時任務根據發貨狀態進行發貨流程驅動 定時任務補償再次發貨
    發貨成功則分佈式事務最終一致,下游發貨系統進行發貨防重處理 發貨失敗進入逆向流程
    定時任務驅動發貨最終一致理論上可以一直進行,但是發貨可能有一個流程時效和庫存售罄的問題,可以根據業務場景進行配置比如2天內重複調用失敗或調用返回無庫存則進入逆向關單退款流程使得分佈式事務恢復成最開始的一致性狀態
  • Q:逆向流程回滾賬戶扣款,怎麼防重? A:賬戶流水錶做唯一索引正逆向類型 + 業務ID,和賬戶額度表進行本地事務操作,確保只能成功一筆正向/逆向業務操作

場景:廣告任務結算

 
 

項目背景

當一個App有了足夠多的用戶體量,便開始有商家進行廣告或商品的投放,平臺通過包裝運營活動、廣告位等,將用戶曝光與商家付費結合達到流量變現的目的。

分佈式事務

當用戶進行瀏覽、關注、商品購買、視頻觀看、App下載等多種任務,我們會根據商家配置等付費規則進行商家廣告費用的扣減,同時還要爲用戶完成任務進行獎勵結算,此時的分佈式事務便是商家賬戶扣減與用戶賬戶增加的數據一致性問題

設計方案

 
 
  • 交互流程
 
 
  • 補償流程
 
 

場景Q&A

  • Q:商戶扣款失敗或者商戶扣款成功,用戶結算失敗怎麼辦? A: 定時任務根據結算狀態進行結算流程驅動,會一直輪詢補償嘗試結算用戶,直到成功。
  • Q:商戶扣款成功,用戶結算失敗爲何不進行做業務超時的逆向流程回滾商家扣款? A: 用戶做任務時一定是做了前置校驗進行平臺任務發放的,也就是說用戶任務只要完成必須要進行結算,這是一個不能逆向的流程。即使極端情況下商戶餘額空了暫時無法結算到用戶也要一直重試,一旦商戶餘額充足則繼續整個結算流程。

場景:運營活動抽獎

 
 

項目背景

抽獎是運營活動中比較常見的方式,對於用戶來說需要做的是參加設定人物獲取積分,攢夠積分就可以開始抽獎,抽中後即等待獎品入賬,一般券會立刻入賬使用,而實物商品則需要耐心等待物流送到用戶手上。

分佈式事務

關於抽獎,涉及賬戶動賬、庫存參與次數扣減、抽獎等多個環節,該場景要解決的分佈式事務是賬戶動賬、活動庫存變更、抽獎下單數據一致性

設計方案

 
 
  • 交互流程
 
 
  • 補償流程
 
 

場景Q&A

  • Q:動賬扣減失敗,補償流程爲何不驅動扣減完成抽獎? A: 抽獎業務對於用戶來說實時性要求很高,正向流程沒有完成的話,沒有通過補償流程驅動的必要性了,用戶體驗不好容易產生問題,補償流程只針對賬戶扣減成功扣沒有順利完成抽獎的情況進行賬戶補款即可。而且這部分補款是有延遲的,在C端展示可以優化或者忽略合併掉,保證的是賬戶額度無損。

2. 弱一致性

2.1 最大努力通知 + 消息重試控制

場景:數據變更同步下游業務方

 
 

項目背景

系統數據發生變更時,會對外部系統產生一定影響,外部系統需要知道這種數據變化,這便是數據狀態同步的場景。一般來說數據交互可以有推(Push)、拉(Pull) 兩種形式,這裏先說推模式,即數據變更方負責將變化通知到數據關注方。

分佈式事務

這裏要保證的是數據變更在多個應用中的狀態一致

設計方案

 
 
  • 交互流程
 
 

場景Q&A

這裏是弱一致性的實現,沒有做本地事務表和定時任務輪詢對比各事務狀態進行補償操作。完全依賴於MQ的失敗重試驅動,若RPC調用失敗即數據同步業務方失敗,MQ會一直進行重試操作,隨着重試次數增加,重試間隔也會增加,這裏也可以業務自行進行最大努力嘗試次數的控制,超過多少次嘗試仍失敗則放棄,因此不能保證最終一致

場景:數據變更廣播下游業務方

 
 

項目背景

這裏是數據同步的說拉模式,即數據關注方對數據變更方進行數據狀態變更的監聽,這種處理方式處理的主動權在於數據關注方,數據變更方只負責和保證一定通知到數據變更情況,是否能夠同步成功則由監聽方處理和兼容。

分佈式事務

這裏要保證的是數據變更在多個應用中的狀態一致

設計方案

 
 
  • 交互流程
 
 

場景Q&A

這裏也是弱一致性的實現,沒有做本地事務表和定時任務輪詢對比各事務狀態進行補償操作。完全依賴於MQ消費方的處理,若消費方處理失敗或在消息隊列規定時間內沒有消費完畢,則數據無法保證最終一致

2.2 戰略放棄 + 報警 + 人工修復

場景:秒殺庫存回滾

 
 

項目背景

在秒殺場景中,最複雜的除了解決高併發問題外,最核心的就是活動商品的庫存控制、變更問題,一般商品庫存會初始化到Redis緩存中進行管理,秒殺方法會對Redis緩存庫存數量進行校驗、扣減操作,通過MQ異步扣減DB庫存,既利用Reids原子操作進行庫存數量操作,又利用緩存抗住高併發請求,起到異步削峯的作用,這是秒殺的正向流程。而逆向流程是用戶秒殺到商品預佔了庫存,但是沒有及時進行訂單支付或者進行了訂單取消,此時要發起對庫存的恢復操作。

分佈式事務

這裏的分佈式事務是Redis緩存庫存與DB庫存數量一致性問題

設計方案

 
 
  • 交互流程
 
 

場景Q&A

  • Q:秒殺場景會出現哪些分佈式問題? A: 根據如上流程圖,扣減緩存庫存、創建訂單、異步MQ發送是在一個同步的函數方法中的三個非原子的子步驟,而秒殺場景下流量洪峯會一瞬間打滿線程,以上三個子步驟任何異步都會出現問題,因爲都是先扣緩存庫存數量,根據實踐經驗看,極端情況下會出現扣減緩存庫存成功了,後面創建訂單失敗了或者異步MQ沒有發出來無法削減DB庫存數量,因此數據結果是緩存庫存扣減的多,DB扣減的少,實際搶購賣出的少,換句話說就是出現了少賣的現象。
  • Q:會不會出現超賣現象? A: 不會。依賴於Redis單線程命令執行的保證。這裏需要注意的是讀、寫命令不是一致,可以結合分佈式鎖實現,也可以通過Lua腳本實現命令的原子性執行。

這裏也是一個弱一致性的實現,業務場景我們保證不超賣即可,對於極端情況出現的庫存數量無效多扣減做戰略性放棄,一般情況下不會影響大多業務使用,如果非要吹毛求疵達到賬戶金額那種強一致性,思路也很簡單,可以藉助定時任務輪詢對比緩存與DB庫存數量進行校驗,這裏還要考慮到其他在行流程如超市關單庫存恢復,仍然在行的秒殺活動等,保證數據處理不多加不多減。

3. 總結

3.1 分佈式角色

  • 參與者
    可以通俗的認爲是DB、RPC、MQ這些能夠提供事務能力的中間件或接口服務
  • 協調者
    維繫分佈式事務各個參與者分佈式狀態的系統、中間件,如Zookeeper、業務系統

3.2 技術保證

  • 數據庫事務 數據庫如MYSQL提供了2PC、XA協議,依賴於WAL + Redo Log + 刷盤策略保證
  • MQ事務 提供了2PC協議,依賴於Ack機制+刷盤策略保證

3.2 強弱一致選擇

  • 強一致性
    強一致性確保的不是事務一定成功,而是事務參與者的子事務要麼全成功,要麼全失敗,保證子事務的最終一致。一般依賴於定時任務、補償機制、Double Check等方式進行事務狀態的校準和協調,一般設計和實現的複雜度大,參與者越多,流程越複雜,越難以維護,最終一致的延遲性和可靠性保證越難
  • 弱一致性
    弱一致性放棄了最終一致性的保證,通過最大努力實現而不保證最終的結果,這種場景減少和減低了開發和設計的複雜度

3.3 冪等&防重

  • 業務冪等通常會定義bizId代表全局唯一的業務標識。在MQ重發、重複消費、亂序,RPC重複調用等場景進行業務防重兼容處理。
  • 如賬戶餘額的強一致防重處理,可以結合流水錶唯一索引正逆向類型 + 業務ID進行攔截
  • 一般大多依賴於數據庫的唯一索引進行防重保證,如果擔心數據庫性能問題,可以前置緩存攔截處理

3.4 儘早干預&補償一致

  • 儘早干預

指的是代碼邏輯上儘早對串行處理的做個子事務進行回滾或逆向操作,這樣可以儘快結束分佈式事務,而不需要等待相對更爲延遲的定時任務或其他補償機制來驅動,這裏可以使用旁路方法或不阻塞主方法放到MQ或異步線程中進行處理,比如秒殺下單發貨因爲庫存不足或商品下架可以立刻進行發起關單退款的逆向流程

  • 補償一致

補償機制一般可以通過定時任務、MQ重試來進行子事務驅動整個分佈式事務的完結

 

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