接口冪等性:
一、什么是冪等性
接口冪等性就是用戶對於同一操作發起的一次請求或者多次請求的結果是一致的,不會因為多次點擊而產生了副作用﹔比如說支付場景,用戶購買了商品支付扣款成功,但是返回結果的時候網絡異常,此時錢已經扣了,用戶再次點擊按鈕,此時會進行第二次扣款,返回結果成功,用戶查詢余額返發現多扣錢了,流水記錄也變成了兩條...,這就沒有保證接口的冪等性。
二、哪些情況需要防止
用戶多次點擊按鈕
用戶頁面回退再次提交
微服務互相調用,由於網絡問題,導致請求失敗。feign觸發重試機制其他業務情況
三、什么情況下需要冪等
以SQL為例,有些操作是天然冪等的。
SELECT * FROM table WHER id=?,無論執行多少次都不會改變狀態,是天然的冪等。
UPDATE tab1 SET col1=1 WHERE col2=2,無論執行成功多少次狀態都是一致的,也是冪等操作。delete from user where userid=1,多次操作,結果一樣,具備冪等性
insert into user(userid,name) values(1,a')如userid為唯一主鍵,即重復操作上面的業務,只會插入一條用戶數據,具備冪等性。
UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次執行的結果都會發生變化,不是冪等的。insert into user(userid,name) values(1, a')如userid不是主鍵,可以重復,那上面業務多次操作,數據都會新增多條,不具備冪等性。
四、冪等解決方案
方案一:token機制
1、服務端提供了發送token 的接口。我們在分析業務的時候,哪些業務是存在冪等問題的,就必須在執行業務前,先去獲取token,服務器會把token保存到redis中。
2、然后調用業務接口請求時,把token攜帶過去,一般放在請求頭部。
3、服務器判斷token是否存在redis,中,存在表示第一次請求,然后刪除token,繼續執行業務。
4、如果判斷token 不存在redis 中,就表示是重復操作,直接返回重復標記給client,這樣就保證了業務代碼,不被重復執行。
危險性;
1、先除token還是后刪除token;
(1)先刪除可能導致,業務確實沒有執行,重試還帶上之前token,由於防重設計導致,請求還是不能執行。
(2)后刪除可能導致,業務處理成功,但是服務閃斷,出現超時,沒有刪除token,別
人繼續重試,導致業務被執行兩邊
(3)我們最好設計為先刪除token,如果業務調用失敗,就重新獲取token再次請求。2、Token獲取、比較和刪除必須是原子性
(1) redis.get(token)、token.equals、redis.del(token)如果這兩個操作不是原子,可能導致,高並發下,都get 到同樣的數據,判斷都成功,繼續業務並發執行
(2)可以在redis使用lua腳本完成這個操作
if redis.call(get' , KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
方案二:各種鎖機制
1、數據庫悲觀鎖
select * from xoox where id = 1 for update;
悲觀鎖使用時一般伴隨事務一起使用,數據鎖定時間可能會很長,需要根據實際情況選用。另外要注意的是, id字段一定是主鍵或者唯一索引,不然可能造成鎖表的結果,處理起來會非常麻煩。
2、數據庫樂觀鎖
這種方法適合在更新的場景中,
updatet_ goods set count = count -1 , version = version+ 1 where good_ id=2 and version= 1 根據version版本,也就是在操作庫存前先獲取當前商品的version版本號,然后操作的時候帶上此version號。我們梳理下,我們第一次操作庫存時,得到version為1,調用庫存服務version變成了2;但返回給訂單服務出現了問題,訂單服務又一次發起調用庫存服務,當訂單服務傳如的version還是1,再執行上面的sql語句時,就不會執行;因為version已經變為2了,where條件就不成立。這樣就保證了不管調用幾次,只會真正的處理一次。樂觀鎖主要使用於處理讀多寫少的問題
方案三:各種唯一約束
1、數據庫唯一約束
插入數據,應該按照唯一索引進行插入,比如訂單號,相同的訂單就不可能有兩條記錄插入。我們在數據庫層面防止重復。
這個機制是利用了數據庫的主鍵唯一約束的特性,解決了在insert場景時冪等問題。但主鍵的要求不是自增的主鍵,這樣就需要業務生成全局唯一的主鍵。
如果是分庫分表場景下,路由規則要保證相同請求下,落地在同一個數據庫和同一表中,要不然數據庫主鍵約束就不起效果了,因為是不同的數據庫和表主鍵不相關。
2、redis set防重
很多數據需要處理,只能被處理一次,比如我們可以計算數據的MD5將其放入redis 的set,每次處理數據,先看這個MD5是否已經存在,存在就不處理。
方案四:防重表
使用訂單號 orderNo做為去重表的唯一索引,把唯一索引插入去重表,再進行業務操作,且他們在同一個事務中。這個保證了重復請求時,因為去重表有唯一約束,導致請求失敗,避免了冪等問題。這里要注意的是,去重表和業務表應該在同一庫中,這樣就保證了在同一個事務,即使業務操作失敗了,也會把去重表的數據回滾。這個很好的保證了數據一致性。
之前說的 redis防重也算
方案五:全局請求唯一id
調用接口時,生成一個唯一id,redis 將數據保存到集合中(去重),存在即處理過。可以使用nginx設置每一個請求的唯一id;
proxy_set_header X-Request-ld Srequest_id;