冪等的概念
大部分文章都會說,同一個操作,進行多次操作後,結果是一樣的,就可以說這個操作是支持冪等的。感覺不太準確,比如一個http get操作,可能每次的結果都不一樣,但是其實是冪等的。看了很多文章,感覺下面的定義比較準確:
一個操作如果多次任意執行所產生的影響(或者叫副作用),都是相同的。
創建訂單的冪等
如果一個用戶分兩次下單,購買的商品都是一樣的。
第一次請求:user1:購買一個商品product1;
第二次請求:user1:還是購買一個商品product1;
這種場景也很常見,是需要生成兩個訂單的。這樣子看起來貌似創建訂單的接口做不了冪等,因爲業務數據一樣的情況下,還是需要生成多個訂單。但是這樣子設計還是有個坑,萬一創建訂單的接口超時了呢?並且調用方進行了重試的話,那就可能變成用戶其實想下一個單,但是訂單系統其實生成了多個訂單。比如說:
調用方發起創建訂單的請求,訂單系統收到了,併成功創建訂單了。但是由於系統原因或者網絡原因等,沒有及時告知調用方訂單已經創建成功,調用方一直等待回覆,直到超時了。調用方再次發起了創建訂單的請求,這個時候就可能會生成多個訂單。
如果訂單接口不支持冪等的情況下,如何應付這種情況呢?有兩種方法
第一種:
當調用方調用訂單接口超時了,是會收到異常的,這個時候調用方捕獲到這個異常後,
不要進行重試操作了,調用訂單的一個回滾接口,將訂單取消掉。
雖然看起來很low,但是還是有人這麼做的。
第二種:
讓訂單系統提供一個訂單是否創建成功的查詢接口,根據一些關鍵業務字段去查詢,如果查詢到已經創建成功了,則調用方不要重試了。
上面兩種方案都有人用過,但是都沒實現冪等。其實針對上面的場景,用冪等來設計也不是很難。可以使用一個唯一的流水號ID,用來標識是不是同一個請求或者交易。這種ID通常都需要具備全局唯一性
。假設讓客戶端來生成這個ID,每個創建訂單的請求生成一個唯一的ID。那麼訂單系統如何根據來實現冪等呢?通常有兩種。
第一種:
先將這個ID保存到一個流水錶裏面,並且流水錶中將這個ID設置爲
UNIQUE KEY
,如果插入出現衝突了,則說明這個創建訂單的請求已經處理過了,直接返回之前的操作結果。
第二種:
根據ID讀取流水錶,如果沒有讀取到,則創建訂單和插入流水錶。如果讀取到了,則返回之前的操作結果。
不建議使用第二種方式,因爲大部分情況下的請求都不是重試來的,讓100%的請求都要去讀取流水錶,實在是不應該。另外,讀取流水錶的操作也是有潛在風險的,因爲用數據庫的讀檢查來確保數據存在性可能因爲競爭而不生效,存在競態條件。
建議用第一種方案,因爲本來流水錶就是要插入,順便利用UNIQUE KEY
的衝突特性來判斷。
現在我們用第一種方案完整描述一下整個處理過程。
當調用方攜帶流水號ID調用創建訂單的接口,如果出現超時了,調用方不知道訂單到底創建成功還是失敗,這個時候,用
同一個
流水號進行重試,訂單系統雖然收到了兩個請求,但是由於流水號ID是同一個,可以根據流水錶來做冪等操作。並告知對方訂單創建成功與否。
這裏又有一個坑,萬一調用方進行重試的時候,重新生成一個流水號,那就沒得救了,會生成多個訂單了。這個只能讓客戶端來保證了。
關於多重冪等
假設創建訂單的接口在創建訂單的時候,還需要依賴一些外部系統,如果訂單創建接口實現了冪等,但是外部接口沒有實現冪等的話,還是可能出現冪等漏洞
。屬於整個鏈路冪等的問題了。好複雜。目前還沒想好如何處理這種情況呢。
思考題
調用方創建唯一ID,服務端用流水錶這種方式實現冪等,非常依賴這個唯一ID。萬一這個ID丟失了呢?咋破?目前我也在思考這個問題。