在系統設計的時候,操作冪等設計是一點需要考慮的點。
冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。在編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。
用數學表達式來表達的話:
f(x) = f (f (x))
1、數據庫冪等
冪等性是後續多餘的調用不會對系統數據的一致性進行破壞。在數據庫操作一般會有增、刪、查、改 4 類操作。下面我們來看這 4 種操作的冪等性:
- select : 查詢操作天生冪等,不管做一次查詢還是多次查詢都是冪等
- insert : 添加數據天生不冪等,多次添加就會造成髒數據
- delete : 對於主鍵刪除或者條件刪除一般都是冪等的
- update : 修改操作可能是冪等(update table set x = 3 where x = 2),也可能是不冪等的(update table set x = x + 1 where id = 1)
所以針對系統處理,如果需要考慮冪等設計。只需要考慮添加操作以及修改操作。
2、添加操作冪等
因爲添加操作不是冪等的,如果需要保證操作冪等就需要系統編碼的時候自己考慮冪等。下面就給出一個具體的場景,這樣才能更好的理解。如果需要提供接口給第三方調用,憑證系統 就是一個常見的系統。憑證系統主要是以下幾個作用:
- 保證調用方的冪等,防止重複調用
- 保存調用方的原始數據,發生爭執可以利用憑證數據
- 外部訂單號轉化爲內部訂單號
可以添加一張記錄表根據商戶號與外部訂單號做爲數據庫的唯一主鍵來保證數據的冪等。
在保存數據的時候要考慮兩個異常:
- 多次保存商戶憑證由於違背了數據庫的唯一主鍵而報錯
- 操作業務的出錯
第一次出錯,數據庫事務能夠保證不會保存髒數據。如果保存了商戶憑證後然後業務操作進行了出錯。返回錯誤信息給商戶,如果商戶再次嘗試就會出錯。所以必須保證,保存商戶憑證與業務操作必須在一個事務裏面。
用僞代碼可以如下表示:
// 0、開始事務
start transaction;
try {
// 1 保存憑證
save voucher;
// 2 業務操作
func(biz);
} catch (Exception e) {
// 3、回滾事務
roll back;
}
// 4、提交事務
commit stransaction;
這樣就可以根據數據庫的唯一主鍵來保證添加操作冪等。
3、修改操作冪等
對於修改操作,很多情況下是不冪等的。比如,當一個點餐訂單的時候,支付成功即是這個訂單完成了。支付一般是調用第三方支付平臺,如:微信、支付寶等。當調用支付的時候支付成功通知地址傳遞給第三方支付平臺。
支付成功後,第三方平臺就會回調這個支付成功地址。調用的方式分爲同步與異步 ,不論是同步還是異步都可能存在超時的情況。爲了支付重試,回調接口必須保證冪等。
- 根據傳入的訂單號來查詢業務訂單並鎖定。
- 首先檢測訂單狀態如果狀態是已經支付就會直接發起退款
- 如果訂單是待支付狀態,修改支付狀態並且保存支付訂單號與支付方式到訂單中
這裏面就是通過數據庫的悲觀鎖來做這件事,具體僞代碼如下:
// 1、開始事務
begin transaction;
// 2、鎖住數據庫記錄
select * from order where order_id = ? for update;
// 3、業務操作
bizOperate(); // 修改訂單狀態,支付訂單號與支付方式
// 4、提交事務
commit transaction;
在使用悲觀鎖來做數據冪等的時候需要考慮以下幾點:
- 需要注意的是鎖數據庫的查詢語句必須是在索引上。如果查詢語句沒有在索引上就會鎖定整個表,這個是一件非常恐怖的事
- 當使用
for update
的時候,就相當於 Java 裏面的synchronized
關鍵字,它是獨當的。所以在其它數據來訪問的時候就會阻塞,這對於高併發系統來說是不可忍受的。所以可以使用for update nowait
,如果其它資源來查詢的時候就會拋出異常。
4、總結
業務系統實現冪等性的方式基本確定。系統關鍵接口的冪等性爲以後系統的長期發展,特別是往分佈式方向發展打下了很好的根基,可以大大簡化分佈式應用的構建複雜度。