引子
群里發了一個總共1千元的拼手氣紅包,共10個。靜兒點進去,額,搶到了0.05元。這個不甘心啊。退出來重新打開了這個紅包,你猜怎樣?顯示我搶到了0.05元!
這就是冪等(idempotence),不管多少次請求某一個資源,對資源都具有相同的影響。冪等性是系統的接口對外一種承諾,承諾只要調用接口成功,外部多次調用對系統只產生一次副作用。
為什么要冪等
世界上最遙遠的距離是我終於鼓起勇氣,對着馬路對面的你大喊:“你願意娶我嗎?”我看到你面帶燦爛的笑容,正回答的時候……一輛大卡車駛過,你的回答我沒有聽見。
因各種不可抗因素產生的沒有收到響應,一個簡單有效的方法就是重試。被重試的接口必須是冪等的。
冪等性是分布式系統設計中的一個重要概念,對超時處理、系統恢復等具有重要意義。
保證冪等的手段
保證冪等需要理清楚兩件事情:冪等條件和期望結果。
大家可能聽說過保證冪等的手段有token令牌、分布式鎖、去重表、數據庫唯一索引等。這些所謂的冪等手段實際上防重手段。防重本質是防止一個相同的請求被當成多個不同的請求來處理。冪等的條件是知道這是一個相同的請求。防重和冪等本質上是兩個不同的階段。
狀態機冪等
在支付場景中,創建了一個支付訂單,發起了一個支付請求,這個訂單不論多少次重復請求,都應該保證最多只扣款一次。即
相同支付訂單ID(冪等條件) —> 最多一次扣款(期望結果)
為了實現這個目標,可以考慮使用有限狀態機。
有限狀態機(Finite-state machine FSM),又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。用於處理復雜的狀態轉換。
在這個支付的例子中,為了化簡,不考慮退款、取消訂單等復雜的狀態,只考慮未支付和已支付兩種狀態之間的轉換。
由上面的狀態轉換圖可以看到,相同支付訂單ID從未支付狀態,要不就是支付不成功停留在未支付狀態,要不就是支付成功,狀態轉移為已支付。此狀態轉移過程不可逆。
public enum OrderStateEnum {
UNPAID {
@Override
public OrderStateEnum changeState() {
if (doPay()) {
return PAID;
}
return UNPAID;
}
},
PAID {
@Override
public OrderStateEnum changeState() {
return PAID;
}
};
public abstract OrderStateEnum changeState();
public boolean doPay() {
//這里是邏輯偽代碼,可以是發起下游調用請求支付通道等
return true;
}
}
這是一個java版本的簡單狀態機實現。狀態機里定義了一個未支付狀態和其行為changeState。changeState又定義了一個未支付狀態和其行為changeState。
利用狀態機來實現這個冪等支付請求的設計流程圖如下:
參考狀態機實現和上圖可知,相同支付ID的請求,支付狀態只能進行一次從未支付到已支付的轉換。從而保證了其冪等性。
按目標冪等
先來回答一個小學生的問題:
定了一個會議,參加人數為10人。發現會議室的椅子只有5把。3個提前來到會議室的同學熱心的去其他地方搬椅子進來。問:每人要搬幾把椅子?
有人要說這不是把簡單的問題復雜了嗎?大家看到椅子不夠就去搬,看夠10把椅子了就不搬就可以了。對了,這其實是一個很好的解題思路,完全可以用在設計當中,就是按目標冪等。
相同會議ID(冪等條件) —> 總數10把椅子(期望結果)
利用按目標冪等來實現這個總數10把椅子請求的設計流程圖如下:
采用按目標的設計,相同會議ID,不管多少次請求,請求椅子的總數就是10把。多次請求不改變行為,從而實現了冪等。
總結
支持冪等是一個接口的基本素養