創建訂單實現冪等的一點思考

冪等的概念


大部分文章都會說,同一個操作,進行多次操作後,結果是一樣的,就可以說這個操作是支持冪等的。感覺不太準確,比如一個http get操作,可能每次的結果都不一樣,但是其實是冪等的。看了很多文章,感覺下面的定義比較準確:

一個操作如果多次任意執行所產生的影響(或者叫副作用),都是相同的。


創建訂單的冪等


如果一個用戶分兩次下單,購買的商品都是一樣的。

第一次請求:user1:購買一個商品product1;
第二次請求:user1:還是購買一個商品product1;

這種場景也很常見,是需要生成兩個訂單的。這樣子看起來貌似創建訂單的接口做不了冪等,因爲業務數據一樣的情況下,還是需要生成多個訂單。但是這樣子設計還是有個坑,萬一創建訂單的接口超時了呢?並且調用方進行了重試的話,那就可能變成用戶其實想下一個單,但是訂單系統其實生成了多個訂單。比如說:

調用方發起創建訂單的請求,訂單系統收到了,併成功創建訂單了。但是由於系統原因或者網絡原因等,沒有及時告知調用方訂單已經創建成功,調用方一直等待回覆,直到超時了。調用方再次發起了創建訂單的請求,這個時候就可能會生成多個訂單。

如果訂單接口不支持冪等的情況下,如何應付這種情況呢?有兩種方法

第一種:

當調用方調用訂單接口超時了,是會收到異常的,這個時候調用方捕獲到這個異常後,不要進行重試操作了,調用訂單的一個回滾接口,將訂單取消掉。雖然看起來很low,但是還是有人這麼做的。

第二種:

讓訂單系統提供一個訂單是否創建成功的查詢接口,根據一些關鍵業務字段去查詢,如果查詢到已經創建成功了,則調用方不要重試了。

上面兩種方案都有人用過,但是都沒實現冪等。其實針對上面的場景,用冪等來設計也不是很難。可以使用一個唯一的流水號ID,用來標識是不是同一個請求或者交易。這種ID通常都需要具備全局唯一性。假設讓客戶端來生成這個ID,每個創建訂單的請求生成一個唯一的ID。那麼訂單系統如何根據來實現冪等呢?通常有兩種。

第一種:

先將這個ID保存到一個流水錶裏面,並且流水錶中將這個ID設置爲UNIQUE KEY,如果插入出現衝突了,則說明這個創建訂單的請求已經處理過了,直接返回之前的操作結果。

第二種:

根據ID讀取流水錶,如果沒有讀取到,則創建訂單和插入流水錶。如果讀取到了,則返回之前的操作結果。

不建議使用第二種方式,因爲大部分情況下的請求都不是重試來的,讓100%的請求都要去讀取流水錶,實在是不應該。另外,讀取流水錶的操作也是有潛在風險的,因爲用數據庫的讀檢查來確保數據存在性可能因爲競爭而不生效,存在競態條件。

建議用第一種方案,因爲本來流水錶就是要插入,順便利用UNIQUE KEY的衝突特性來判斷。

現在我們用第一種方案完整描述一下整個處理過程。

當調用方攜帶流水號ID調用創建訂單的接口,如果出現超時了,調用方不知道訂單到底創建成功還是失敗,這個時候,用同一個流水號進行重試,訂單系統雖然收到了兩個請求,但是由於流水號ID是同一個,可以根據流水錶來做冪等操作。並告知對方訂單創建成功與否。

這裏又有一個坑,萬一調用方進行重試的時候,重新生成一個流水號,那就沒得救了,會生成多個訂單了。這個只能讓客戶端來保證了。


關於多重冪等


假設創建訂單的接口在創建訂單的時候,還需要依賴一些外部系統,如果訂單創建接口實現了冪等,但是外部接口沒有實現冪等的話,還是可能出現冪等漏洞。屬於整個鏈路冪等的問題了。好複雜。目前還沒想好如何處理這種情況呢。


思考題


調用方創建唯一ID,服務端用流水錶這種方式實現冪等,非常依賴這個唯一ID。萬一這個ID丟失了呢?咋破?目前我也在思考這個問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章