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

Sam哥哥發表於2018-04-10

冪等的概念

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

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

建立訂單的冪等

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

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

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

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

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

第一種:

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

第二種:

讓訂單系統提供一個訂單是否建立成功的查詢介面,根據一些關鍵業務欄位去查詢,如果查詢到已經建立成功了,則呼叫方不要重試了。

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

第一種:

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

第二種:

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

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

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

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

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

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

關於多重冪等

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

思考題

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

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

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

相關文章