接口幂等性思路
概念
接口冪等性就是用戶對于統一操作發起的一次請求或多次請求的結果是一致的,不會因為多次點擊而產生了副作用。
哪些場景需要保證接口的冪等性?
用戶多次點擊按鈕。
用戶頁面回退再次提交
微服務之間相互調用,由于網絡波動卡頓,導致feign觸發重試機制。
其他情況...
天然冪等情況
以sql為例:
對于select * from table where id =?這種場景,無論執行多少次,都是冪等的。
update table set col =? where col2 = ?,也是冪等的
delete from table where id =? 也是冪等的
insert into table(id,name....) values (1,name,...),如果id是唯一主鍵,那么該操作也是冪等的。
冪等解決方案
token機制
1.服務端提供發送token接口,需要冪等的接口,就在執行業務前,獲取token,服務器將token保存到redis中。
2.然后調用業務接口時,將token放在請求頭中。
3.服務器判斷token是否存在redis中,存在表示第一次請求,然后刪除token,繼續執行業務。
4.如果判斷token不在redis中,就是重復操作,直接返回重復標記給client,就保證業務不被重復執行。
危險性:
1.先刪除token還是后刪除token
先刪除,網絡閃斷等原因導致業務確實沒有執行,然后觸發重試機制,由于防重設計,請求不能被執行
后刪除,業務處理成功,但服務閃斷,出現超時,沒有刪除token,別人繼續重試,導致業務執行多次。
最好的設計為先刪除token,如果業務調用失敗,重新獲取token再次請求。
2.token獲取、比較和刪除必須為原子性
redis.get(token)、redis.equals、redis.del(token),如果這些操作不是原子性的,高并發情況下,可能get到同樣數據,判斷都成功,繼續業務
可以使用lua腳本保證redis操作的原子性
if redis.call('get',KEYS[1]) == ARGV[1]
then return redis.call('del',KEYS[1])
else return 0 end
鎖機制
悲觀鎖
select * from table where id = ? for update;
悲觀鎖使用時一般伴隨著事務一起使用,數據庫鎖定的時間可能會有點長。
注意:id必須時主鍵或者時唯一索引,不然會導致鎖表的結果。
樂觀鎖
主要適用于讀多寫少的場景。
更新場景:
例如:update table set col = ? ,version= version+1 where id=? and version =1
可以根據version版本號,操作的時候需要帶上version。
當第一次執行后,version變成2后,再次執行上述sql,where條件不成立,也保證了冪等性。
分布式鎖
多個機器同時處理相同數據,我們可以加上分布式鎖(redis或者zookeeper等),同一時間,只有一個機器能拿到分布式鎖,執行業務,處理完成后,釋放鎖,獲取到鎖的時候必須判斷該業務是否處理過,如果是,則不處理。
唯一約束
數據庫唯一約束
插入數據,應該按照唯一索引進行插入,相同的唯一索引只能有一條,可以在數據庫中防止重復。但是要保證在同一個業務下發多次請求都生成全局唯一的主鍵。
分庫分表場景時,要保證相同請求落地到同一數據庫同一張表。
redis set集合防重復
計算數據的MD5,放入set集合中,每次處理,先看MD5是否已經存在,存在則不處理。
防重表
專門新建一個數據表作為防重表。處理業務時,先將唯一索引(例如訂單號)插入防重表,在進行業務操作,并且在同一事務中。
全局請求唯一id
調用接口時,生成一個唯一id,redis將id存在set中,存在則處理過。
可以使用nginx設置每一個請求的唯一id。
proxy_set_header X-Request-Id $request_id
此外,我們通過$request_id 實現客戶端->網關服務器->微服務集群A->>微服務集群B.... 實現日志串聯。通過trace_id回顯,跟蹤每次調用路由。
總結
- 上一篇: Angulary应用依赖里的platfo
- 下一篇: 日积跬步02