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