淺談java接口的冪等性及解決方案
一、什么情況下需要冪等
用戶多次點擊按鈕
用戶頁面回退再次提交
微服務(wù)相互調(diào)用,由于網(wǎng)絡(luò)問題,導(dǎo)致請求失敗,feign觸發(fā)重試機制
二、冪等性解決方案
2.1 token機制(令牌)
在加載頁面詳情時候,服務(wù)器會順便生成一個token一起返回給前端,服務(wù)端同時也在Redis中保存這個token數(shù)據(jù),前端并不展示這個token,但當(dāng)頁面點擊提交按鈕時候,會在攜帶上這個token參數(shù),此時后端便會先校驗前端提交請求的token與redis中的token是否一致,一致的話即是第一次請求,執(zhí)行業(yè)務(wù)代碼并在Redis中刪除該token,當(dāng)用戶還是攜帶上次的驗證碼多次提交,此時服務(wù)器判斷redis中驗證碼不存在,便可能是多次提交的情況,不再執(zhí)行業(yè)務(wù)代碼,保證業(yè)務(wù)的冪等性,不被重復(fù)執(zhí)行。
問題1:先刪除token還是后刪除token
先刪除可能導(dǎo)致, 業(yè)務(wù)確實沒有執(zhí)行,重試還帶上之前token,由于防重設(shè)計導(dǎo)致,請求還是不能執(zhí)行。后刪除可能導(dǎo)致,業(yè)務(wù)處理成功,但是服務(wù)閃斷,出現(xiàn)超時,沒有刪除token,別人繼續(xù)重試,導(dǎo)致業(yè)務(wù)被執(zhí)行兩遍
解決:我們最好設(shè)計為先刪除token,如果業(yè)務(wù)調(diào)用失敗,就重新獲取token再次請求。
問題2:如何保證token 獲取、比較和刪除必須是原子性
redis.get(token) 、token.equals、 redis del(token)如果這兩個操作不是原子,可能導(dǎo)致,高并發(fā)下,都get到同樣的數(shù)據(jù),判斷都成功,繼續(xù)業(yè)務(wù)并發(fā)執(zhí)行
解決:可以在redis使用lua腳本完成這個操作
// redis+lua腳本 原子驗證令牌防止重復(fù)提交攻擊 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; String orderToken = "現(xiàn)在的令牌"; // return 0 失敗 1 成功 Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),Arrays.asList("要驗證的KEY"), orderToken);
2.2 各種鎖機制
1)數(shù)據(jù)庫悲觀鎖
//0.開始事務(wù) begin; //1.查詢出商品信息 select status from t_goods where id=1 for update; //2.根據(jù)商品信息生成訂單 insert into t_orders (id,goods_id) values (null,1); //3.修改商品status為2 update t_goods set status=2; //4.提交事務(wù) commit;
2)數(shù)據(jù)庫樂觀鎖
適用讀多寫少的情況
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
3)業(yè)務(wù)層分布式鎖
redison可以解決,代碼實現(xiàn)
@Override @RedisLock(lockedPrefix = "model:replace:materialId:", timeOut = 5000) @Transactional(rollbackFor = Exception.class) public void changeSpuSync(@LockedObject Integer materialId, String userName) { log.info("模型表監(jiān)聽物料檔案id={}是否修改了spu", materialId); ModelChangeSpuReq modelChangeSpuReq = new ModelChangeSpuReq(materialId, userName); this.dealChangeSpuSyncModel(modelChangeSpuReq); }
2.3 各種唯一約束
1)數(shù)據(jù)庫的唯一約束:
利用主鍵的唯一性
2)redis set防重:
很多數(shù)據(jù)需要處理,只能被處理一次,比如我們可以計算數(shù)據(jù)的MD5將其放入redis的set,每次處理數(shù)據(jù),先看這個MD5是否已經(jīng)存在,存在就不處理。(百度上傳文件秒傳機制,如果該文件已經(jīng)存在,就無需重復(fù)上傳)
2.4 防重表
使用訂單號orderNo做為去重表的唯一索引, 把唯一索引插入去重表, 再進行業(yè)務(wù)操作,且他們在同一個事中。這個保證了重復(fù)請求時,因為去重表有唯一約束,導(dǎo)致請求失敗,避免了冪等問題。這里要注意的是,去重表和業(yè)務(wù)表應(yīng)該在同一庫中,這樣就保證了在同一個事務(wù),即使業(yè)務(wù)操作失敗了,也會把去重表的數(shù)據(jù)回滾。這個很好的保證了數(shù)據(jù)一致性。
2.5 全局請求唯一id
前端每次調(diào)用接口請求時,生成一個唯一id,redis將數(shù)據(jù)保存到集合中(去重),存在即處理過。
全鏈路tranceId:可以使用Nginx設(shè)置每一個請求的唯一id;也可方便系統(tǒng)的鏈路追蹤,該id并不能解決去重問題,當(dāng)A系統(tǒng)調(diào)用B系統(tǒng),B系統(tǒng)是否產(chǎn)生重試機制時候,可以根據(jù)這個id去判斷
proxy_set header X-Request-ld $request_id;
總結(jié)
總之,一般情況下,幾種方式的優(yōu)選級使用順序可以這樣:分布式鎖 > 樂觀鎖 > JVM鎖 > 唯一約束 > 數(shù)據(jù)庫悲觀鎖
當(dāng)然,冪等性設(shè)計不能脫離業(yè)務(wù)實際來討論,一定要根據(jù)實際業(yè)務(wù)場景選擇合適的冪等性解決方案。
到此這篇關(guān)于淺談java接口的冪等性及解決方案的文章就介紹到這了,更多相關(guān)java接口的冪等性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳述IntelliJ IDEA遠程調(diào)試Tomcat的方法(圖文)
本篇文章主要介紹了詳述IntelliJ IDEA遠程調(diào)試Tomcat的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12Java將一個正整數(shù)分解質(zhì)因數(shù)的代碼
這篇文章主要介紹了將一個正整數(shù)分解質(zhì)因數(shù)。例如:輸入90,打印出90=2*3*3*5,需要的朋友可以參考下2017-02-02詳解Springboot 優(yōu)雅停止服務(wù)的幾種方法
這篇文章主要介紹了詳解Springboot 優(yōu)雅停止服務(wù)的幾種方法 ,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Activiti工作流學(xué)習(xí)筆記之自動生成28張數(shù)據(jù)庫表的底層原理解析
這篇文章主要介紹了Activiti工作流學(xué)習(xí)筆記之自動生成28張數(shù)據(jù)庫表的底層原理解析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03