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