冪等性
今天我們來談談什么是冪等性?
引用百度百科的解析如下:
冪等(idempotent、idempotence)是一個數(shù)學與計算機學概念,常見于抽象代數(shù)中。
在編程中一個冪等操作的特點是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。冪等函數(shù),或冪等方法,是指可以使用相同參數(shù)重復執(zhí)行,并能獲得相同結(jié)果的函數(shù)。這些函數(shù)不會影響系統(tǒng)狀態(tài),也不用擔心重復執(zhí)行會對系統(tǒng)造成改變。例如,“setTrue()”函數(shù)就是一個冪等函數(shù),無論多次執(zhí)行,其結(jié)果都是一樣的.更復雜的操作冪等保證是利用唯一交易號(流水號)實現(xiàn)。
這解析,確實有點長了,大家話看看就行了?。?!(●'?'●)
那對于我們程序員來說,我們關心的更多是下面這些問題:
什么地方,什么場景下需要用到冪等?
冪等,我們需要怎么做,如何實現(xiàn)冪等呢?
如何實現(xiàn)冪等呢
基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
什么場景下需要用到冪等
前端表單重復提交問題
用戶訂單支付問題
銀行業(yè)務辦理取號問題
用戶惡意進行調(diào)接口問題
接口超時重復提交問題
MQ消息進行重復消費
當然了,還有很多場景會用到冪等,這里咱們就不一一列舉出來了。
那我們要如何設計一個冪等功能呢,而且還是代碼非侵入式?
代碼非侵入式的意思,就是,我們的業(yè)務邏輯代碼,不需要處理冪等校驗的邏輯。
業(yè)務功能不處理?那交給誰處理呢?別著急,聽哥們一一道來。^_^
業(yè)務功能不處理?
這里,要實現(xiàn)代碼非侵入式的冪等校驗,我們就要使用到切面編程了(@Aspect)
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
冪等的實現(xiàn)原理
在系統(tǒng)中一些接口需要增加冪等處理,冪等的概念是一個業(yè)務請求只能執(zhí)行一次。類似銀行業(yè)務辦理,首先需要取一個號,然后用戶使用這個號去柜臺辦理業(yè)務。這個號只能使用一次,如果過期或者已辦理這個號就無效了。
我們的冪等也是使用這種原理。
1.首先客戶端調(diào)用通過我們的系統(tǒng)獲取一個號,我們稱之為冪等號,這個號已經(jīng)存在我們的系統(tǒng)中。
2.客戶端使用這個號,調(diào)用我們的接口。
3.我們系統(tǒng)判斷這個號在我們的系統(tǒng)中已經(jīng)存在,如果存在則允許業(yè)務辦理,如果不存在,則表示這是一個非法的號,我們直接拋出異常。
4.當業(yè)務處理完成,我們會將這個號從我們的系統(tǒng)中刪除掉。
好了,這實現(xiàn)步驟,也是十分清晰了呀?。?!^_^
那么我們下面就來看代碼如何實現(xiàn)了
冪等的代碼實現(xiàn)
定義一個冪等處理接口
public?interface?Idempotence?{ ????/** ?????*?檢查是否存在冪等號 ?????*?@param?idempotenceId?冪等號 ?????*?@return?是否存在 ?????*/ ????boolean?check(String?idempotenceId); ????/** ?????*?記錄冪等號 ?????*?@param?idempotenceId?冪等號 ?????*/ ????void?record(String?idempotenceId); ????/** ?????*?記錄冪等號 ?????*?@param?idempotenceId?冪等號 ?????*?@param?time?過期時間 ?????*/ ????void?record(String?idempotenceId,?Integer?time); ????/** ?????*?刪除冪等號 ?????*?@param?idempotenceId?冪等號 ?????*/ ????void?delete(String?idempotenceId); }
定義一個冪等處理接口實現(xiàn)類
@Component
public?class?RedisIdempotence?implements?Idempotence?{
????@Autowired
????private?RedisRepository?redisRepository;
????@Override
????public?boolean?check(String?idempotenceId)?{
????????return?redisRepository.exists(idempotenceId);
????}
????@Override
????public?void?record(String?idempotenceId)?{
????????redisRepository.set(idempotenceId,"1");
????}
????@Override
????public?void?record(String?idempotenceId,Integer?time)?{
????????redisRepository.setExpire(idempotenceId,"1",time);
????}
????@Override
????public?void?delete(String?idempotenceId)?{
????????redisRepository.del(idempotenceId);
????}
}
這個實現(xiàn)類,咱們就用redis存儲這個冪等號 實現(xiàn)4個方法:
檢查是否存在冪等號
記錄冪等號
記錄冪等號(帶過期時間)
刪除冪等號
冪等工具類
@Component
public?class?IdempotenceUtil?{
????@Autowired
????private?RedisRepository?redisRepository;
????/**
?????*?生成冪等號
?????*?@return
?????*/
????public?String?generateId()?{
????????String?uuid?=?UUID.randomUUID().toString();
????????String?uId=Base64Util.encode(uuid).toLowerCase();
????????redisRepository.setExpire(uId,"1",1800);
????????return?uId;
????}
????/**
?????*?從Header里面獲取冪等號
?????*?@return
?????*/
????public?String?getHeaderIdempotenceId(){
????????ServletRequestAttributes?attributes?=?(ServletRequestAttributes)?RequestContextHolder.getRequestAttributes();
????????HttpServletRequest?request?=?attributes.getRequest();
????????String?idempotenceId=request.getHeader("idempotenceId");
????????return?idempotenceId;
????}
}
這個工具類,提供兩個方法。
1.生成一個冪等號,咱們就用uuid
2.從Header里面獲取冪等號
定義一個注解
/**
?*?接口增加冪等性
?*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public?@interface?IdempotenceRequired?{
}
切面
@Aspect
@Slf4j
@Component
public?class?IdempotenceSupportAdvice?{
????@Autowired
????private?Idempotence?idempotence;
????@Autowired
????IdempotenceUtil?idempotenceUtil;
????/**
?????*?攔截有@IdempotenceRequired?注解?的方法。
?????*/
????@Pointcut("@annotation(xxx.xxx.IdempotenceRequired)")
????public?void?idempotenceMethod(){}
????@AfterThrowing(value?=?"idempotenceMethod()()",throwing?=?"e")
????public?void?afterThrowing(Throwable?e){
????????if(!(e?instanceof?IdempotencyException))?{
????????????//?從HTTP?header中獲取冪等號idempotenceId
????????????String?idempotenceId?=?idempotenceUtil.getHeaderIdempotenceId();
????????????idempotence.record(idempotenceId,?1800);
????????}
????}
????@Around(value?=?"idempotenceMethod()")
????public?Object?around(ProceedingJoinPoint??joinPoint)?throws?Throwable?{
????????//?從HTTP?header中獲取冪等號idempotenceId
????????String?idempotenceId?=?idempotenceUtil.getHeaderIdempotenceId();
????????if(StringUtils.isEmpty(idempotenceId)){
????????????//不存在冪等號則不進行額外操作
????????????return?joinPoint.proceed();
????????}
????????//?前置操作?冪等號是否存在
????????boolean?existed?=?idempotence.check(idempotenceId);
????????if?(!existed)?{
????????????throw?new?IdempotencyException("{success:false,message:"操作重復,請重新輸入冪等號重試!",data:-2}");
????????}
????????//刪除冪等號
????????idempotence.delete(idempotenceId);
????????Object?result?=?joinPoint.proceed();
????????return?result;
????}
}
定義個controller
@RequestMapping("/idempotence")
public?class?IdempotenceController?{
????/**
?????*?生成冪等號
?????*?@return
?????*/
????@GetMapping("/generateId")
????public?JsonResult?generateId(){
????????IdempotenceUtil?idempotenceUtil=SpringUtil.getBean(IdempotenceUtil.class);
????????String?uId=idempotenceUtil.generateId();
????????return?JsonResult.success("成功生成!").setData(uId);
????}
}
好了,實現(xiàn)的代碼,就是這些了,理解起來也是比較簡單,沒有過多復雜的邏輯。
接下來,就是如何使用的問題了,
使用的問題
這個使用,也是十分的簡單啦?。?!
冪等的使用
「服務端:」
不是所有的方法都需要切面攔截 ,只有 IdempotenceRequired 注解的方法才會被攔截。
例如下面接口:
@IdempotenceRequired
@PostMapping("/getUsers")
public?JsonResult?getUsers(){
????//執(zhí)行正常業(yè)務邏輯
????...
}
在開發(fā)冪等接口時,只需要在方法上簡單增加一個 IdempotenceRequired 注解即可。
這基本上就是代碼非侵入式了呀!?。?/p>
代碼非侵入式
「客戶端:」
服務端處理好后,在客戶端訪問接口的時候需要執(zhí)行以下步驟:
需要先獲取冪等號
然后將冪等號添加到請求頭中
1.獲取冪等號http://服務地址/idempotence/generateIdhttp://xn--zfry9hnb732h/idempotence/generateId

獲取冪等號
2.請求調(diào)用
往header中添加冪等號

往header中添加冪等號
好了,到這里冪等的實現(xiàn),就已經(jīng)完成了!??!^_^
那我們就可以愉快的編寫代碼了?。?!^_^
編輯:黃飛
電子發(fā)燒友App




















評論