一、什么情況下需要冪等
用戶多次點擊按鈕
用戶頁面回退再次提交
微服務相互呼叫,由于網路問題,導致請求失敗,feign觸發重試機制
二、冪等性解決方案
2.1 token機制(令牌)
在加載頁面詳情時候,服務器會順便生成一個token一起回傳給前端,服務端同時也在Redis中保存這個token資料,前端并不展示這個token,但當頁面點擊提交按鈕時候,會在攜帶上這個token引數,此時后端便會先校驗前端提交請求的token與redis中的token是否一致,一致的話即是第一次請求,執行業務代碼并在Redis中洗掉該token,當用戶還是攜帶上次的驗證碼多次提交,此時服務器判斷redis中驗證碼不存在,便可能是多次提交的情況,不再執行業務代碼,保證業務的冪等性,不被重復執行,
問題1:先洗掉token還是后洗掉token
先洗掉可能導致, 業務確實沒有執行,重試還帶上之前token,由于防重設計導致,請求還是不能執行,后洗掉可能導致,業務處理成功,但是服務閃斷,出現超時,沒有洗掉token,別人繼續重試,導致業務被執行兩遍
解決:我們最好設計為先洗掉token,如果業務呼叫失敗,就重新獲取token再次請求,
問題2:如何保證token 獲取、比較和洗掉必須是原子性
redis.get(token) 、token.equals、 redis del(token)如果這兩個操作不是原子,可能導致,高并發下,都get到同樣的資料,判斷都成功,繼續業務并發執行
解決:可以在redis使用lua腳本完成這個操作
// redis+lua腳本 原子驗證令牌防止重復提交攻擊
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
String orderToken = "現在的令牌";
// return 0 失敗 1 成功
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),Arrays.asList("要驗證的KEY"), orderToken);
2.2 各種鎖機制
1)資料庫悲觀鎖
//0.開始事務
begin;
//1.查詢出商品資訊
select status from t_goods where id=1 for update;
//2.根據商品資訊生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;
//4.提交事務
commit;
2)資料庫樂觀鎖
適用讀多寫少的情況
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
3)業務層分布式鎖
redison可以解決,代碼實作
@Override
@RedisLock(lockedPrefix = "model:replace:materialId:", timeOut = 5000)
@Transactional(rollbackFor = Exception.class)
public void changeSpuSync(@LockedObject Integer materialId, String userName) {
log.info("模型表監聽物料檔案id={}是否修改了spu", materialId);
ModelChangeSpuReq modelChangeSpuReq = new ModelChangeSpuReq(materialId, userName);
this.dealChangeSpuSyncModel(modelChangeSpuReq);
}
2.3 各種唯一約束
1)資料庫的唯一約束:
利用主鍵的唯一性
2)redis set防重:
很多資料需要處理,只能被處理一次,比如我們可以計算資料的MD5將其放入redis的set,每次處理資料,先看這個MD5是否已經存在,存在就不處理,(百度上傳檔案秒傳機制,如果該檔案已經存在,就無需重復上傳)
2.4 防重表
? 使用訂單號orderNo做為去重表的唯一索引, 把唯一索引插入去重表, 再進行業務操作,且他們在同一個事中,這個保證了重復請求時,因為去重表有唯一約束,導致請求失敗,避免了冪等問題,這里要注意的是,去重表和業務表應該在同一庫中,這樣就保證了在同一個事務,即使業務操作失敗了,也會把去重表的資料回滾,這個很好的保證了資料一致性,
2.5 全域請求唯一id
前端每次呼叫介面請求時,生成一個唯一id,redis將資料保存到集合中(去重),存在即處理過,
全鏈路tranceId:可以使用Nginx設定每一個請求的唯一id;也可方便系統的鏈路追蹤,該id并不能解決去重問題,當A系統呼叫B系統,B系統是否產生重試機制時候,可以根據這個id去判斷
proxy_set header X-Request-ld $request_id;
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/352566.html
標籤:其他
