淺談java接口的冪等性及解決方案
一、什么情況下需要冪等
用戶多次點(diǎn)擊按鈕
用戶頁(yè)面回退再次提交
微服務(wù)相互調(diào)用,由于網(wǎng)絡(luò)問(wèn)題,導(dǎo)致請(qǐng)求失敗,feign觸發(fā)重試機(jī)制
二、冪等性解決方案
2.1 token機(jī)制(令牌)
在加載頁(yè)面詳情時(shí)候,服務(wù)器會(huì)順便生成一個(gè)token一起返回給前端,服務(wù)端同時(shí)也在Redis中保存這個(gè)token數(shù)據(jù),前端并不展示這個(gè)token,但當(dāng)頁(yè)面點(diǎn)擊提交按鈕時(shí)候,會(huì)在攜帶上這個(gè)token參數(shù),此時(shí)后端便會(huì)先校驗(yàn)前端提交請(qǐng)求的token與redis中的token是否一致,一致的話即是第一次請(qǐng)求,執(zhí)行業(yè)務(wù)代碼并在Redis中刪除該token,當(dāng)用戶還是攜帶上次的驗(yàn)證碼多次提交,此時(shí)服務(wù)器判斷redis中驗(yàn)證碼不存在,便可能是多次提交的情況,不再執(zhí)行業(yè)務(wù)代碼,保證業(yè)務(wù)的冪等性,不被重復(fù)執(zhí)行。
問(wèn)題1:先刪除token還是后刪除token
先刪除可能導(dǎo)致, 業(yè)務(wù)確實(shí)沒(méi)有執(zhí)行,重試還帶上之前token,由于防重設(shè)計(jì)導(dǎo)致,請(qǐng)求還是不能執(zhí)行。后刪除可能導(dǎo)致,業(yè)務(wù)處理成功,但是服務(wù)閃斷,出現(xiàn)超時(shí),沒(méi)有刪除token,別人繼續(xù)重試,導(dǎo)致業(yè)務(wù)被執(zhí)行兩遍
解決:我們最好設(shè)計(jì)為先刪除token,如果業(yè)務(wù)調(diào)用失敗,就重新獲取token再次請(qǐng)求。
問(wèn)題2:如何保證token 獲取、比較和刪除必須是原子性
redis.get(token) 、token.equals、 redis del(token)如果這兩個(gè)操作不是原子,可能導(dǎo)致,高并發(fā)下,都get到同樣的數(shù)據(jù),判斷都成功,繼續(xù)業(yè)務(wù)并發(fā)執(zhí)行
解決:可以在redis使用lua腳本完成這個(gè)操作
// redis+lua腳本 原子驗(yàn)證令牌防止重復(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("要驗(yàn)證的KEY"), orderToken);
2.2 各種鎖機(jī)制
1)數(shù)據(jù)庫(kù)悲觀鎖
//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ù)庫(kù)樂(lè)觀鎖
適用讀多寫少的情況
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
3)業(yè)務(wù)層分布式鎖
redison可以解決,代碼實(shí)現(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ù)庫(kù)的唯一約束:
利用主鍵的唯一性
2)redis set防重:
很多數(shù)據(jù)需要處理,只能被處理一次,比如我們可以計(jì)算數(shù)據(jù)的MD5將其放入redis的set,每次處理數(shù)據(jù),先看這個(gè)MD5是否已經(jīng)存在,存在就不處理。(百度上傳文件秒傳機(jī)制,如果該文件已經(jīng)存在,就無(wú)需重復(fù)上傳)
2.4 防重表
使用訂單號(hào)orderNo做為去重表的唯一索引, 把唯一索引插入去重表, 再進(jìn)行業(yè)務(wù)操作,且他們?cè)谕粋€(gè)事中。這個(gè)保證了重復(fù)請(qǐng)求時(shí),因?yàn)槿ブ乇碛形ㄒ患s束,導(dǎo)致請(qǐng)求失敗,避免了冪等問(wèn)題。這里要注意的是,去重表和業(yè)務(wù)表應(yīng)該在同一庫(kù)中,這樣就保證了在同一個(gè)事務(wù),即使業(yè)務(wù)操作失敗了,也會(huì)把去重表的數(shù)據(jù)回滾。這個(gè)很好的保證了數(shù)據(jù)一致性。
2.5 全局請(qǐng)求唯一id
前端每次調(diào)用接口請(qǐng)求時(shí),生成一個(gè)唯一id,redis將數(shù)據(jù)保存到集合中(去重),存在即處理過(guò)。
全鏈路tranceId:可以使用Nginx設(shè)置每一個(gè)請(qǐng)求的唯一id;也可方便系統(tǒng)的鏈路追蹤,該id并不能解決去重問(wèn)題,當(dāng)A系統(tǒng)調(diào)用B系統(tǒng),B系統(tǒng)是否產(chǎn)生重試機(jī)制時(shí)候,可以根據(jù)這個(gè)id去判斷
proxy_set header X-Request-ld $request_id;
總結(jié)
總之,一般情況下,幾種方式的優(yōu)選級(jí)使用順序可以這樣:分布式鎖 > 樂(lè)觀鎖 > JVM鎖 > 唯一約束 > 數(shù)據(jù)庫(kù)悲觀鎖
當(dāng)然,冪等性設(shè)計(jì)不能脫離業(yè)務(wù)實(shí)際來(lái)討論,一定要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景選擇合適的冪等性解決方案。
到此這篇關(guān)于淺談java接口的冪等性及解決方案的文章就介紹到這了,更多相關(guān)java接口的冪等性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳述IntelliJ IDEA遠(yuǎn)程調(diào)試Tomcat的方法(圖文)
本篇文章主要介紹了詳述IntelliJ IDEA遠(yuǎn)程調(diào)試Tomcat的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12Java將一個(gè)正整數(shù)分解質(zhì)因數(shù)的代碼
這篇文章主要介紹了將一個(gè)正整數(shù)分解質(zhì)因數(shù)。例如:輸入90,打印出90=2*3*3*5,需要的朋友可以參考下2017-02-02Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析
這篇文章主要介紹了Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12Java中 URL實(shí)現(xiàn)斷點(diǎn)下載
Java中 URL實(shí)現(xiàn)斷點(diǎn)下載,需要的朋友可以參考一下2013-03-03JAVA(MAVEN項(xiàng)目)添加JUnit依賴配置全過(guò)程
在Maven項(xiàng)目中進(jìn)行單元測(cè)試是確保代碼質(zhì)量的重要步驟,本教程提供SpringBoot和微服務(wù)平臺(tái)適用的單元測(cè)試方法,包括環(huán)境準(zhǔn)備、創(chuàng)建測(cè)試類、JUnit簡(jiǎn)介及注解使用,環(huán)境準(zhǔn)備涉及引入依賴和安裝JUnit插件,測(cè)試類創(chuàng)建可通過(guò)快捷鍵或手動(dòng)添加@Test注解來(lái)實(shí)現(xiàn)2024-10-10詳解Springboot 優(yōu)雅停止服務(wù)的幾種方法
這篇文章主要介紹了詳解Springboot 優(yōu)雅停止服務(wù)的幾種方法 ,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Activiti工作流學(xué)習(xí)筆記之自動(dòng)生成28張數(shù)據(jù)庫(kù)表的底層原理解析
這篇文章主要介紹了Activiti工作流學(xué)習(xí)筆記之自動(dòng)生成28張數(shù)據(jù)庫(kù)表的底層原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03