本地消息表實現最終一致性

背景

傳統的單體應用不會橫跨多個數據庫,能夠經過單機事務保證一致性。然而在海量數據的場景下,我須要對數據庫作拆分,即分庫分表,而CobarMyCat這類分庫分表中間並不提供分佈式事務的特性,而且基於二階段提交的分佈式事務性能較差,對於大多數業務場景來講,並不須要強一致,只須要保證最終一致性便可。數據庫

實踐

下面咱們舉個下訂單的場景,總共有3個實體,商品用戶訂單,咱們按照user_id來sharding。因此相同user_id用戶訂單在同一個物理庫下,而商品表中不存在user_id,因此商品表在不一樣的物理庫下。segmentfault

下訂單的場景,主要涉及到兩個事務操做,扣減庫存生成訂單,由於兩個操做涉及不一樣的數據庫,因此沒法保證強一致性。網絡

咱們能夠經過本地消息表,來實現最終一致性,具體流程以下圖:分佈式

alt text

  • 調用外部服務,生成全局惟一的交易流水號trans_id
  • 事務一:1) 扣減庫存 2) 根據流水單號,生成對應商品的凍結記錄。消息表主要由商品ID交易流水號凍結數消息狀態這四個字段構成,由於消息表和商品表在同一個物理庫下,因此TX1中的操做1和操做2是能夠構成事務操做的。凍結記錄的狀態有三種:已凍結釋放已售出釋放未售出。凍結記錄的初始狀態爲已凍結
  • 事務一若是成功,則進行事務二;若是事務一失敗,則直接返回。
  • 事務二:根據交易流水號trans_id生成訂單,訂單的狀態有三種:未支付已支付超時,訂單的初始狀態爲未支付
  • 若訂單建立成功,則進行後續的支付流程。
  • 若是事務二失敗,因爲網絡抖動超時等緣由,不必定是真的生成訂單失敗,即 在事務二失敗的狀況下,可能生成了訂單,也可能確實沒有生成訂單。
  • 定時任務一:設置一個每隔15分鐘的定時任務(即一個訂單必須在15分鐘內完成支付),從訂單表裏撈出最近半小時內的全部訂單,對每個訂單作以下處理:若訂單超時未支付,開啓事務SELECT FOR UPDATE 鎖住該訂單,即用悲觀鎖阻止用戶對訂單進行支付等操做,而後經過訂單的trans_id去凍結表更新對應凍結記錄的狀態,置爲釋放未售出,並回滾商品數量,回滾商品的操做完成後,將訂單狀態置爲超時,若事務中調用的回滾商品數量的服務失敗,則能夠發出報警人工處理,或經過更長時間的定時任務去處理;若訂單爲已支付,則將凍結表中記錄的狀態置爲釋放已售出
  • 定時任務二:由於存在事務一成功,而事務二的訂單確實沒有建立成功的狀況,這樣會凍結一部分商品的數量,因此能夠撈取出 建立超過10分鐘 狀態爲已凍結的全部凍結記錄,根據每一個凍結記錄的trans_id去訂單表查詢,若不存在對應的訂單,則將凍結記錄的狀態更新爲釋放未售出,並回滾商品數量。
  • 另外一個須要注意的點,在定時任務一中,對於超時未支付的訂單,會先回滾凍結表,而後將訂單狀態置爲超時,但這最後一步將訂單置爲超時可能會失敗,這樣會出現不一致的狀態,即訂單狀態爲未支付,而凍結記錄的狀態爲釋放未售出。因此,在支付的時候須要作一個前置校驗,檢查凍結記錄的狀態是否爲已凍結,若不是,則拒絕支付。

變種

在上面這種模型的基礎上,還有一種變種,以下圖:性能

alt text

即在TX2失敗的狀況下,跳轉到TX3spa

  • 根據trans_id查詢訂單,若訂單不存在,則直接將凍結記錄置爲釋放未售出,並回滾庫存;若訂單存在,則說明TX2由於網絡抖動等緣由而失敗,其實訂單建立成功,則進行正常的支付流程。
  • 須要注意的是:根據trans_id查詢訂單的時候,必定要開啓事務,這樣纔會強制走主庫,若不開啓事務,則會走備庫,由於MySQL主從同步延遲的問題,備庫極可能沒法查詢到訂單,從而回滾庫存,這顯然是錯誤的。

變種的優勢

將定時任務的壓力均勻地分配到每一次調用中,提升數據庫的可用性。code

總結

在不須要強一致性的業務場景下,均可以經過定時任務+冪等操做來實現最終一致性。blog

以上。事務

原文連接

https://segmentfault.com/a/11...rem

相關文章
相關標籤/搜索