實現業務冪等性的常用方案!
重復請求
生產環境經常出現重復的數據,而這個絕大部分原因是發生了重復的請求。
重復請求是指同一個請求因為某些原因被多次提交。
導致這個情況會有幾種場景:
微服務場景:
- 在微服務架構下,接口超時,微服務框架會進行重試。
用戶交互的時候多次點擊,如:快速點擊按鈕多次。
MQ消息中間件:消息重復消費。
第三方平臺接口:因為異常也會導致多次異步回調。
冪等性定義
如上,針對重復請求,我們在設計某些接口時,要考慮如何保證接口冪等。
那什么是接口冪等呢?
定義:多次調用對系統的產生的影響是一樣的,即對資源的作用是一樣的,但是返回值允許不同。
- 并且不會因為多次點擊而產生了副作用。
比如支付場景:
用戶購買了商品支付扣款成功,但是返回結果的時候網絡異常,此時錢已經扣了。
用戶再次點擊按鈕,此時會進行第二次扣款,返回結果成功。
- 如果用戶查詢余額返發現多扣錢了,流水記錄也變成了兩條,這就沒有保證接口的冪等性。
冪等場景
查詢
- 不會對數據產生任何變化,具備冪等性。
新增
- 數據都會新增多條,不具備冪等性。
修改
- 直接賦值更新,具備冪等性。
- 計算賦值更新,不具備冪等性。
刪除
- 多次操作,結果一樣,具備冪等性。
總結:新增沒有唯一主鍵約束的數據,和計算賦值更新操作都不具備冪等性。
直接賦值:
update user set num = 20 where userid=123;
計算賦值:
update user set num = num + 20 where userid=123;
冪等性方案
悲觀鎖
id字段一定是主鍵或者唯一索引,不然可能造成鎖表的結果。
- 數據鎖定時間可能會很長,需要根據實際情況選用。
select * from user where id = 1 for update;
樂觀鎖
加上了版本號后,計算賦值更新場景,具備了冪等性。
缺點:
- 在操作業務前,需要先查詢出當前的version版本。
update user set num= num + 20, version = version + 1 where userid=123 and version=1;
唯一約束
利用數據庫的主鍵唯一約束的特性,解決在insert場景時冪等問題。
去重表
把唯一主鍵插入去重表,再進行業務操作,且他們在同一個事務中。
- 這個保證了重復請求時,因為去重表有唯一約束,導致請求失敗,避免了冪等問題。
去重表和業務表應該在同一庫中,這樣就保證了在同一個事務,即使業務操作失敗了,也會把去重表的數據回滾。
- 保證了數據一致性。
去重表是跟業務無關的,很多業務可以共用同一個去重表。
- 需要規劃好唯一主鍵。
圖片
分布式鎖
如果多個機器可能在同一時間同時處理相同的數據:
- 比如多臺機器定時任務都拿到了相同數據處理。
就可以加分布式鎖,鎖定此數據,處理完成后釋放鎖。
- 獲取到鎖的必須先判斷這個數據是否被處理過。
Token機制
圖片
?
服務端提供了發送token的接口。
在執行業務前,先去獲取token,服務器會把token保存到redis中。
- 然后調用業務接口請求時,把token攜帶過去。
服務器判斷token是否存在redis中,存在表示第一次請求。
- 這時把redis中的token刪除,繼續執行業務。
如果判斷token不存在redis中,就表示是重復操作,直接返回重復標記給client。
- 這樣就保證了業務代碼,不被重復執行。
缺點
業務請求每次請求,都會有額外的請求:
- 一次獲取token請求、判斷token是否存在。
真實的生產環境中,1萬請求也許只會存在10個左右的請求會發生重試。
- 為了這10個請求,需要讓9990個請求都發生了額外的請求。
邏輯實現:
可以通過自定義注解將進行改造。
- 在需要保證冪等的方法上,添加自定義注解即。
圖片
總結
冪等性是系統服務對外一種承諾,特別業務中涉及的錢的部分,一定要慎重再慎重。
雖然前端做限制會更容易點,但前后端都需要做努力。