Spring Boot 接口冪等插件使用
冪等概述
冪等性原本是數(shù)學(xué)上的概念,即使公式:f(x)=f(f(x)) 能夠成立的數(shù)學(xué)性質(zhì)。用在編程領(lǐng)域,則意為對(duì)同一個(gè)系統(tǒng),使用同樣的條件,一次請(qǐng)求和重復(fù)的多次請(qǐng)求對(duì)系統(tǒng)資源的影響是一致的。
冪等性是分布式系統(tǒng)設(shè)計(jì)中十分重要的概念,具有這一性質(zhì)的接口在設(shè)計(jì)時(shí)總是秉持這樣的一種理念:調(diào)用接口發(fā)生異常并且重復(fù)嘗試時(shí),總是會(huì)造成系統(tǒng)所無(wú)法承受的損失,所以必須阻止這種現(xiàn)象的發(fā)生。
實(shí)現(xiàn)冪等的方式很多,目前基于請(qǐng)求令牌機(jī)制適用范圍較廣。其核心思想是為每一次操作生成一個(gè)唯一性的憑證,也就是 token。一個(gè) token 在操作的每一個(gè)階段只有一次執(zhí)行權(quán),一旦執(zhí)行成功則保存執(zhí)行結(jié)果。對(duì)重復(fù)的請(qǐng)求,返回同一個(gè)結(jié)果(報(bào)錯(cuò))等。參考《冪等性淺談》[1]
冪等處理實(shí)現(xiàn)
加入依賴
????com.pig4cloud.plugin
????idempotent-spring-boot-starter
????0.0.1
配置 Redis 鏈接
默認(rèn)情況下,可以不配置。理論是支持 redisson-spring-boot-starter[2] 全部配置
spring:
??redis:
????host:?127.0.0.1
????port:?6379
接口
@Idempotent(key?=?"#key",?expireTime?=?10,?info?=?"請(qǐng)勿重復(fù)查詢")
@GetMapping("/test")
public?String?test(String?key)?{
????return?"success";
}
測(cè)試
10 個(gè)獨(dú)立線程請(qǐng)求

執(zhí)行查看結(jié)果,10 個(gè)請(qǐng)求只會(huì)有一個(gè)成功

查看后臺(tái)異常報(bào)錯(cuò),9 個(gè)異常報(bào)錯(cuò)滿足預(yù)期

idempotent 注解說(shuō)明
key: 冪等操作的唯一標(biāo)識(shí),使用 spring el 表達(dá)式 用#來(lái)引用方法參數(shù) 。可為空則取當(dāng)前 url + args 做請(qǐng)求的唯一標(biāo)識(shí)
expireTime: 有效期 默認(rèn):1 有效期要大于程序執(zhí)行時(shí)間,否則請(qǐng)求還是可能會(huì)進(jìn)來(lái)
timeUnit: 時(shí)間單位 默認(rèn):s (秒)
info: 冪等失敗提示信息,可自定義
delKey: 是否在業(yè)務(wù)完成后刪除 key true:刪除 false:不刪除
冪等處理設(shè)計(jì)原理
流程設(shè)計(jì)參考[3]
1.請(qǐng)求開(kāi)始前,根據(jù) key 查詢 查到結(jié)果:報(bào)錯(cuò) 未查到結(jié)果:存入 key-value-expireTime key=ip+url+args
2.請(qǐng)求結(jié)束后,直接刪除 key 不管 key 是否存在,直接刪除 是否刪除,可配置
3.expireTime 過(guò)期時(shí)間,防止一個(gè)請(qǐng)求卡死,會(huì)一直阻塞,超過(guò)過(guò)期時(shí)間,自動(dòng)刪除 過(guò)期時(shí)間要大于業(yè)務(wù)執(zhí)行時(shí)間,需要大概評(píng)估下;
4.此方案直接切的是接口請(qǐng)求層面。
5.過(guò)期時(shí)間需要大于業(yè)務(wù)執(zhí)行時(shí)間,否則業(yè)務(wù)請(qǐng)求 1 進(jìn)來(lái)還在執(zhí)行中,前端未做遮罩,或者用戶跳轉(zhuǎn)頁(yè)面后再回來(lái)做重復(fù)請(qǐng)求 2,在業(yè)務(wù)層面上看,結(jié)果依舊是不符合預(yù)期的。
6.建議 delKey = false。即使業(yè)務(wù)執(zhí)行完,也不刪除 key,強(qiáng)制鎖 expireTime 的時(shí)間。預(yù)防 5 的情況發(fā)生。
7.實(shí)現(xiàn)思路:同一個(gè)請(qǐng)求 ip 和接口,相同參數(shù)的請(qǐng)求,在 expireTime 內(nèi)多次請(qǐng)求,只允許成功一次。
8.頁(yè)面做遮罩,數(shù)據(jù)庫(kù)層面的唯一索引,先查詢?cè)偬砑樱忍幚矸绞綉?yīng)該都處理下。
9.此注解只用于冪等,不用于鎖,100 個(gè)并發(fā)這種壓測(cè),會(huì)出現(xiàn)問(wèn)題,在這種場(chǎng)景下也沒(méi)有意義,實(shí)際中用戶也不會(huì)出現(xiàn) 1s 或者 3s 內(nèi)手動(dòng)發(fā)送了 50 個(gè)或者 100 個(gè)重復(fù)請(qǐng)求,或者弱網(wǎng)下有 100 個(gè)重復(fù)請(qǐng)求;
總結(jié)
pig-mesh/pig
pig-mesh/idempotent-spring-boot-starter
參考資料
參考《冪等性淺談》: https://www.jianshu.com/p/475589f5cd7b
[2]redisson-spring-boot-starter: https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter
[3]流程設(shè)計(jì)參考: https://github.com/it4alla/idempotent
