分佈式事務案例

常見的分佈式事務處理方式有:2PC、TCC、異步確保型,2PC的處理方式,在以前的《Spring系列(9)-多數據源和2PC分佈式事務》中已經寫過,本文針對後二者分享。前端

一、本地消息表(異步確保)

1.一、特色

本地消息表與業務數據表處於同一個數據庫中,這樣就能利用本地事務來保證在對這兩個表的操做知足事務特性,而且使用了消息隊列來保證 最終一致性數據庫

  1. 在分佈式事務操做的一方完成寫業務數據的操做以後向本地消息表發送一個消息,本地事務能保證這個消息必定會被寫入本地消息表中。
  2. 以後將本地消息表中的消息轉發到 Kafka 等消息隊列中,若是轉發成功則將消息從本地消息表中刪除,不然繼續從新轉發。「消息隊列」 能夠用 「輪詢程序」 替代,目的是能夠經過不斷重試,保障事務最終執行成功。
  3. 在分佈式事務操做的另外一方從消息隊列中讀取一個消息,並執行消息中的操做。該操做必須都是知足冪等性的,冪等性是指同一個操做不管請求多少次,其結果都相同。

1.二、案例

在一個業務系統中,某個主流程節點觸發流轉操做時,須要同時執行其餘多個系統事務(如:安裝一站式App在oa流程流轉時,須要在sap系統中翻轉狀態)。只有當下列全部其餘系統的事務都成功完成後,主流程業務才流轉成功:小程序

  • 調用A系統的接口,完成事務A。
  • 調用B系統的接口,完成事務B。
  • 調用C系統的接口,完成事務C。

1.三、步驟

  1. (本地訂單表)即主業務系統的業務表,有惟一標識的「業務流水號」。
  2. (本地消息表)主業務系統流程流轉時,本地消息表中分別寫入須要執行A系統、B系統、C系統3個不一樣事務的3條消息,消息的狀態爲【處理中】。因爲都是在同一本地數據庫,可保證事務一致性。
  3. (事務A)A系統有」處理事務A的接口」,該接口保障冪等性。經過 「輪詢程序」 遍歷全部【處理中】狀態下A事務的消息調接口,事務完成後修改消息狀態;或經過 「消息隊列」,保障事務最終被成功執行後消費消息。

    3.1. 針對該「業務流水號」,若是A系統中未處理,則處理該事務,並更新A系統消息的狀態爲【已處理】。segmentfault

    3.2. 針對該「業務流水號」,若是A系統中已處理,則不處理該事務。微信小程序

    3.3. 假設:A系統是某sap系統,須要將某」業務流水號「對應的物料數量「加一」,當前物料數量基礎數量是5。」處理事務A的接口」便是sap系統開發並提供的接口,該sap接口判斷,若是並未處理該「業務流水號」的事務,則將物料基礎數量更新爲6;若是已處理過該「業務流水號」的事務,則物料基礎數量不變。由於「冪等性」,不會致使每次調接口都會往上「加一」。微信

  4. (事務B)B系統和A系統處理機制相似。
  5. (事務C)C系統和A系統處理機制相似。
  6. (本地訂單表+本地消息表)經過輪詢程序,或經過A/B/C每一個消息的回調通知,觸發調用一個一樣冪等的方法。該方法判斷該「業務流水號」在本地消息表中A、B、C事務的3條消息,是否狀態都爲【已處理】。若是都是【已處理】,則更新本地訂單表中「業務流水號」狀態,流程翻轉成功。因爲都是在同一本地數據庫,可保證事務一致性。

二、微信支付JSAPI(異步確保)

2.一、特色

微信官方提供的操做:app

  • (API)統一下訂單接口。
  • (異步SDK)用戶在微信客戶端上支付該訂單(密碼支付/指紋支付/人臉識別/等等)。
  • (API)用戶異步支付成功後,觸發回調接口,通知商戶號。
  • (API)查詢訂單狀態接口。

在微信支付成功後,一樣會同時執行其餘多個系統事務,此時的「支付成功」操做就等價於案例一中的「某個主流程節點」。可是有些不一樣,因爲該「支付成功」操做是由微信官方平臺異步觸發的,因此可能會回調失敗,也須要咱們不斷重試,已保障最終全部成功支付的訂單都會觸發後續事務操做。框架

2.二、案例

微信小程序使用JSAPI方式微信支付,給飯卡充值。在支付成功後會同時執行其餘多個系統事務,只有當下列全部其餘系統的事務都成功完成後,飯卡充值業務才成功完成:異步

  • 在充值系統中,充值的金額增長到飯卡餘額中。
  • 在帳單系統中,記錄充值/消費記錄。
  • 在通知系統中,推送充值成功的消息(app內通知/短信通知)。

2.三、步驟

  1. (本地訂單表)用戶輸入金額,充值。後臺調【微信統一下單接口】生成微信支付訂單,接口返回成功後,在本地訂單表中建立訂單記錄,狀態爲【支付中】。
  2. 前端異步回調,微信小程序拉起微信SDK組件,用戶開始支付。
  3. (事務P+本地消息表)提供【微信支付成功回調接口】,該接口支持冪等性-若是查詢微信訂單狀態已支付,且本地消息表中無事務處理消息,則執行。執行操做爲,在本地消息表中分別寫入須要執行A、B、C3個不一樣事務的3條消息,消息的狀態爲【處理中】。

    3.1. 將執行事務P的【微信支付成功回調接口】配置在微信下單的接口中,在異步支付成功後調用該接口。若是未收到成功反饋,微信官方會重發回調方法,可是有重發的次數限制。分佈式

    3.2. 「輪詢程序」或「消息隊列」,兩種方案來處理未成功執行了的事務P。重試調用【微信支付成功回調接口】,直到成功支付了的訂單,都能將A、B、C3個不一樣事務的3條消息寫入本地消息表。

  4. (事務P 輪詢)輪詢程序遍歷本地訂單表中【支付中】的訂單,經過【微信訂單查詢接口】查詢訂單狀態:
    4.一、若是訂單已支付,則說明【微信支付成功回調接口】未觸發,或其餘緣由致使的事務異常。自動調用(事務B)中【微信支付成功回調接口】。
    4.二、若是訂單未支付,且未到取消訂單的超時時間,則不處理訂單。
    4.三、若是訂單未支付,且超過取消訂單的超時時間,更新訂單狀態爲【已做廢】。
  5. (事務A)充值系統中提供該接口,有冪等性。針對該惟一標識等訂單流水號,若是已經在帳戶中完成對應充值的餘額更新,則不處理,不然更新帳戶餘額。並更新消息表中對應的消息狀態爲【已處理】。
  6. (事務B)帳單系統中提供該接口,有冪等性。針對該惟一標識等訂單流水號,若是已經在帳單中已記錄該次充值的帳單記錄,則不處理,不然記錄該次充值的帳單記錄。並更新消息表中對應的消息狀態爲【已處理】。
  7. (事務C)通知系統中提供該接口,有冪等性。針對該惟一標識等訂單流水號,若是已經在通知系統中已發送該次充值的消息,則不處理,不然發送該次充值的消息。並更新消息表中對應的消息狀態爲【已處理】。
  8. (本地訂單表+本地消息表)經過輪詢程序,或經過A/B/C每一個消息的回調通知,觸發調用一個一樣冪等的方法。該方法判斷該「業務流水號」在本地消息表中A、B、C事務的3條消息,是否狀態都爲【已處理】。若是都是【已處理】,則更新本地訂單表中「業務流水號」狀態,流程翻轉成功。因爲都是在同一本地數據庫,可保證事務一致性。

2.四、對比

「案例一」中普通的異步確保,和「案例二」中微信支付的異步確保,兩套方案的實現上有些差異。

差異體如今,如何將 「主業務流程觸發」「將A、B、C事務的3個消息寫入消息中間表」 這兩步操做做爲一個事務,並保證它的原子性。

  • 案例一:「主業務流程流轉」,和「將A、B、C事務的3個消息寫入消息中間表」,都是在同一個數據庫下的操做,能夠直接使用數據庫的事務實現。
  • 案例二:「主業務流程觸發」實際對應的是「微信支付成功」,這個是由微信支付官方平臺提供的異步觸發事務,而「將A、B、C事務的3個消息寫入消息中間表」則是本地數據庫事務。兩者自己已是分佈式事務處理了。因此引入了 「事務P」,並提供 「執行事務P的冪等性接口」。經過輪詢或消息隊列來監聽本地訂單表,經過不斷重試該冪等性接口,保證事務的原子性。

三、商品搶購(TCC補償)

3.一、特色

你本來的一個接口,要改造爲3個邏輯,Try-Confirm-Cancel:

  • 先是服務調用鏈路依次執行 Try 邏輯。
  • 若是都正常的話,TCC 分佈式事務框架推動執行 Confirm 邏輯,完成整個事務。
  • 若是某個服務的 Try 邏輯有問題,TCC 分佈式事務框架感知到以後就會推動執行各個服務的 Cancel 邏輯,撤銷以前執行的各類操做。

這就是所謂的 TCC 分佈式事務。TCC 分佈式事務的核心思想,說白了,就是當遇到下面這些狀況時:

  • 某個服務的數據庫宕機了。
  • 某個服務本身掛了。
  • 那個服務的 Redis、Elasticsearch、MQ 等基礎設施故障了。
  • 某些資源不足了,好比說庫存不夠這些。

先來 Try 一下,不要把業務邏輯完成,先試試看,看各個服務能不能基本正常運轉,能不能先凍結我須要的資源。

若是 Try 都 OK,也就是說,底層的數據庫、Redis、Elasticsearch、MQ 都是能夠寫入數據的,而且你保留好了須要使用的一些資源(好比凍結了一部分庫存)。
接着,再執行各個服務的 Confirm 邏輯,基本上 Confirm 就能夠很大機率保證一個分佈式事務的完成了。

那若是 Try 階段某個服務就失敗了,好比說底層的數據庫掛了,或者 Redis 掛了,等等。
此時就自動執行各個服務的 Cancel 邏輯,把以前的 Try 邏輯都回滾,全部服務都不要執行任何設計的業務邏輯。保證你們要麼一塊兒成功,要麼一塊兒失敗。

一、Try: 嘗試執行業務

  • 完成全部業務檢查(一致性)
  • 預留必須業務資源(準隔離性)

二、Confirm: 確認執行業務

  • 真正執行業務
  • 不做任何業務檢查
  • 只使用Try階段預留的業務資源
  • Confirm操做知足冪等性

三、Cancel: 取消執行業務

  • 釋放Try階段預留的業務資源
  • Cancel操做知足冪等性

3.二、案例

一個商品搶購的業務,商品庫存100份,單價6元/份,多人搶購。A用戶帳戶餘額50元,搶購了2份該商品。只有當下列系統的事務都執行成功後纔會搶購完成:

  • 商品系統:檢查當前庫存是否足夠,如足夠,則減去2份商品。
  • 用戶系統:檢查餘額是否足夠,如足夠,則扣除相應金額。
  • 訂單系統:檢查是否知足建立訂單條件,如知足,則建立訂單。

3.三、步驟

3.3.1. Try

  • (事務A-商品)用戶A搶購2份。檢查庫存數量是否大於2,若是庫存充足則繼續進行。更新庫存表中庫存數量爲98,凍結數量爲2。
  • (事務B-用戶)用戶A帳戶餘額50元,購買商品需2元。檢查餘額是否大於2元,若是餘額充足則繼續進行。更新餘額爲48元,凍結2元。
  • (事務C-訂單)用戶A購買商品需建立訂單。檢查是否知足建立訂單條件,若是能夠則繼續進行。建立訂單,但該訂單狀態爲【未生效】。

3.3.2. Confirm

在事務A、B、C都執行成功後,分別調用3個冪等的接口,接口均可經過輪訓或消息隊列等方式重試。

  • (商品)更新凍結數量2爲0。
  • (用戶)更新凍結2元爲0.
  • (訂單)更新訂單狀態爲【有效】。

3.3.3. Cancel:

在Try一直執行失敗後,執行Cancel。分別調用3個冪等的接口,接口均可經過輪訓或消息隊列等方式重試。

  • (庫存)將凍結數量還給庫存數量。
  • (用戶)將凍結金額還給帳戶餘額。
  • (訂單)刪除或做廢當前訂單。
相關文章
相關標籤/搜索