天天看點

分布式資料一緻性(庫存)

庫存當機現狀

目前購物車添加商品、删除商品、修改商品數量、購物車過期庫存解凍、成單後清空購物車,都涉及庫存變化。

以添加商品為例,目前實作邏輯為:

1、調用庫存系統扣減庫存

2、購物車寫庫

3、第2步失敗時,調用庫存系統復原庫存。

以上均為線上同步調用。網絡抖動時,産生的資料不一緻系統無法自動恢複。

購物車與庫存資料一緻性

因購物車和庫存系統分屬不同的資料庫,此處涉及分布式事務問題。

網際網路系統出于系統性能的考慮,一般不追求實時一緻性,而目标轉向最終一緻性。

最終一緻性,可基于消息和處理狀态實作。參考文章

以添加商品為例,實作邏輯仍與現有基本一緻。一個事務可分解為3個動作:當機庫存、購物車寫庫、庫存還原

每個動作都在本地庫進行操作記錄,離線定時對前後兩個動作的操作記錄進行核對,後一動作有缺失則進行重新觸發。

最終一緻性實作設計

概念定義:

1、事務(Transaction):購物車每次添加商品、删除商品、修改商品數量,稱之為一個事務。

2、動作(Action):具體的資料寫入操作,稱之為一個動作。

以資料庫為單元切分,不同資料庫的操作必須切分為不同的動作。同一資料庫的連續操作,可合并為一個動作。

3、連結(Link):前後相鄰兩個動作,需離線進行核對,稱之為連結。先執行的動作簡稱為前動作,後執行的動作簡稱為後動作。

每個動作隻能對應一個連結輸入,可以對應多個連結輸出。

由此,一個事務處理邏輯,可形成一個單向無環圖。圖的節點表示動作,連線表示連結,箭頭方向表示處理先後順序。

分布式資料一緻性(庫存)

具體到購物車與庫存一緻性處理,事務圖可表示如下:

分布式資料一緻性(庫存)

注意:圖中所指結果成功/失敗,一律指業務邏輯上的成敗(如庫存不足)。系統故障(如資料庫連接配接中斷),隻能作為異常處理。

為避免動作重複執行,每個事務需生成一個全局唯一的事務id。全局唯一id生成可參考snowflake

每個資料庫,新增一張事務狀态表transaction_action,表結構字段如下:

1、id:自增id. 主鍵

2、transaction_type:事務類型。例如購物車添加商品為一個事務類型,訂單送出為另一個事務類型

2、transaction_id:事務id。例如每次購物車添加商品,均為一個新的事務

3、action_type:動作類型。例如購物車添加商品,分解為3個動作,就有3個動作類型。

4、input_json:動作輸入參數。為動作處理所需的資料。例如購物車當機庫存,應有參數:商品id、庫存扣減數量,為友善後續處理,還應有user_id

5、output_json:動作輸出結果。為動作後續處理所需的資料。例如購物車當機庫存,應有參數:當機是否成功。為友善後續動作處理,還将輸入參數複制到輸出。

6、output_code:動作結果狀态碼。為友善判斷是否需執行相應後續動作。例如購物車當機庫存,輸出狀态碼即為當機是否成功。

注意:結果成功/失敗,一律指業務邏輯上的成敗(如庫存不足)。系統故障(如資料庫連接配接中斷),隻能作為異常處理,視為該動作未處理。

7、execute_time:動作執行時間點。用于記錄和核對。

以上transaction_type+transaction_id+action_type應建唯一索引, action_type+execute_time+output_code應建索引。

利用資料庫事務,實作業務資料與動作狀态的一緻性。每個動作函數的處理僞代碼如下:

var executed = query(SELECT id FROM transaction_action WHERE transaction_type=? and action_type=? and transaction_id=?);

if executed then return; /檢查動作是否已處理/

… 實際業務進行中的非寫庫處理(例如商品資訊讀取)

BEGIN TRANSACTION

UPDATE stock …… /動作相應的實際業務資料寫庫操作,例如修改商品庫存數/

INSERT transaction_action …… /記錄動作處理狀态。因唯一索引的存在,可完全避免動作重複處理/

COMMIT

… 實際業務進行中的非寫庫處理(例如日志輸出)

離線定時任務進行動作狀态核對。每個連結(一對動作類型),對應一個定時任務。

對比連結前動作記錄,後動作有缺失的,進行重新觸發。定時任務僞代碼如下:

/從前後動作所屬的庫中,讀取近期待核對的動作記錄/

var transactions0 = query(SELECT transaction_id FROM transaction_action WHERE action_type=’前動作’ and execute_time>? and output_code IN ?);

var transactions1 = query(SELECT transaction_id FROM transaction_action WHERE action_type=’後動作’ and execute_time>?);

/計算差異記錄。從前動作記錄中,去除已執行的後記錄即可/

var diffTransaction = transactions0 - transactions1;

/根據差異記錄,逐個後動作重新觸發/

for each transaction in diffTransaction do

{

invoke(transaction);

}

水準分庫的資料庫本地事務處理

目前Java中資料庫通路多用mybatis,并結合spring進行事務管理。一般是使用DataSourceTransactionManager,僅支援單個資料源。

而目前很多基于JDBC驅動的透明水準分庫架構,一般不支援資料庫事務。

可實作一個支援分庫路由的DataSource,每次事務前,先根據分庫字段值進行路由切換。可路由DataSource相關實作可參考。

或者用venus-data(非venus-jdbc),應該支援單庫本地事務。

動作記錄表的分庫規則,應與業務分庫規則一緻,以確定動作記錄和業務資料在同一個庫。

例如購物車‘寫庫’動作記錄,應按user_id分庫。假設庫存按商品id分庫,則相應的’當機庫存’動作記錄,也應按商品id分庫