0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一個注解,優(yōu)雅的實現(xiàn)接口冪等性!

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2023-08-26 14:36 ? 次閱讀


一、什么是冪等性?

簡單來說,就是對一個接口執(zhí)行重復(fù)的多次請求,與一次請求所產(chǎn)生的結(jié)果是相同的,聽起來非常容易理解,但要真正的在系統(tǒng)中要始終保持這個目標,是需要很嚴謹?shù)脑O(shè)計的,在實際的生產(chǎn)環(huán)境下,我們應(yīng)該保證任何接口都是冪等的,而如何正確的實現(xiàn)冪等,就是本文要討論的內(nèi)容。

基于 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/

二、哪些請求天生就是冪等的?

首先,我們要知道查詢類的請求一般都是天然冪等的,除此之外,刪除請求在大多數(shù)情況下也是冪等的,但是ABA場景下除外。

舉一個簡單的例子

比如,先請求了一次刪除A的操作,但由于響應(yīng)超時,又自動請求了一次刪除A的操作,如果在兩次請求之間,又插入了一次A,而實際上新插入的這一次A,是不應(yīng)該被刪除的,這就是ABA問題,不過,在大多數(shù)業(yè)務(wù)場景中,ABA問題都是可以忽略的。

除了查詢和刪除之外,還有更新操作,同樣的更新操作在大多數(shù)場景下也是天然冪等的,其例外是也會存在ABA的問題,更重要的是,比如執(zhí)行update table set a = a + 1 where v = 1這樣的更新就非冪等了。

最后,就還剩插入了,插入大多數(shù)情況下都是非冪等的,除非是利用數(shù)據(jù)庫唯一索引來保證數(shù)據(jù)不會重復(fù)產(chǎn)生。

基于 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/

三、為什么需要冪等

1.超時重試

當(dāng)發(fā)起一次RPC請求時,難免會因為網(wǎng)絡(luò)不穩(wěn)定而導(dǎo)致請求失敗,一般遇到這樣的問題我們希望能夠重新請求一次,正常情況下沒有問題,但有時請求實際上已經(jīng)發(fā)出去了,只是在請求響應(yīng)時網(wǎng)絡(luò)異?;蛘叱瑫r,此時,請求方如果再重新發(fā)起一次請求,那被請求方就需要保證冪等了。

2.異步回調(diào)

異步回調(diào)是提升系統(tǒng)接口吞吐量的一種常用方式,很明顯,此類接口一定是需要保證冪等性的。

3.消息隊列

現(xiàn)在常用的消息隊列框架,比如:Kafka、RocketMQ、RabbitMQ在消息傳遞時都會采取At least once原則(也就是至少一次原則,在消息傳遞時,不允許丟消息,但是允許有重復(fù)的消息),既然消息隊列不保證不會出現(xiàn)重復(fù)的消息,那消費者自然要保證處理邏輯的冪等性了。

四、實現(xiàn)冪等的關(guān)鍵因素

關(guān)鍵因素1

冪等唯一標識,可以叫它冪等號或者冪等令牌或者全局ID,總之就是客戶端與服務(wù)端一次請求時的唯一標識,一般情況下由客戶端來生成,也可以讓第三方來統(tǒng)一分配。

關(guān)鍵因素2

有了唯一標識以后,服務(wù)端只需要確保這個唯一標識只被使用一次即可,一種常見的方式就是利用數(shù)據(jù)庫的唯一索引。

五、注解實現(xiàn)冪等性

下面演示一種利用Redis來實現(xiàn)的方式。

1.自定義注解

importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;

@Target(value=ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceIdempotent{

/**
*參數(shù)名,表示將從哪個參數(shù)中獲取屬性值。
*獲取到的屬性值將作為KEY。
*
*@return
*/
Stringname()default"";

/**
*屬性,表示將獲取哪個屬性的值。
*
*@return
*/
Stringfield()default"";

/**
*參數(shù)類型
*
*@return
*/
Classtype();

}

2.統(tǒng)一的請求入?yún)ο?/h4>
@Data
publicclassRequestData<T>{

privateHeaderheader;

privateTbody;

}


@Data
publicclassHeader{

privateStringtoken;

}

@Data
publicclassOrder{

StringorderNo;

}

3.AOP處理

importcom.springboot.micrometer.annotation.Idempotent;
importcom.springboot.micrometer.entity.RequestData;
importcom.springboot.micrometer.idempotent.RedisIdempotentStorage;
importorg.aspectj.lang.ProceedingJoinPoint;
importorg.aspectj.lang.annotation.Around;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Pointcut;
importorg.aspectj.lang.reflect.MethodSignature;
importorg.springframework.stereotype.Component;

importjavax.annotation.Resource;
importjava.lang.reflect.Method;
importjava.util.Map;

@Aspect
@Component
publicclassIdempotentAspect{

@Resource
privateRedisIdempotentStorageredisIdempotentStorage;

@Pointcut("@annotation(com.springboot.micrometer.annotation.Idempotent)")
publicvoididempotent(){
}

@Around("idempotent()")
publicObjectmethodAround(ProceedingJoinPointjoinPoint)throwsThrowable{
MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();
Methodmethod=signature.getMethod();
Idempotentidempotent=method.getAnnotation(Idempotent.class);

Stringfield=idempotent.field();
Stringname=idempotent.name();
ClassclazzType=idempotent.type();

Stringtoken="";

Objectobject=clazzType.newInstance();
MapparamValue=AopUtils.getParamValue(joinPoint);
if(objectinstanceofRequestData){
RequestDataidempotentEntity=(RequestData)paramValue.get(name);
token=String.valueOf(AopUtils.getFieldValue(idempotentEntity.getHeader(),field));
}

if(redisIdempotentStorage.delete(token)){
returnjoinPoint.proceed();
}
return"重復(fù)請求";
}
}
importorg.aspectj.lang.ProceedingJoinPoint;
importorg.aspectj.lang.reflect.CodeSignature;

importjava.lang.reflect.Field;
importjava.util.HashMap;
importjava.util.Map;

publicclassAopUtils{

publicstaticObjectgetFieldValue(Objectobj,Stringname)throwsException{
Field[]fields=obj.getClass().getDeclaredFields();
Objectobject=null;
for(Fieldfield:fields){
field.setAccessible(true);
if(field.getName().toUpperCase().equals(name.toUpperCase())){
object=field.get(obj);
break;
}
}
returnobject;
}


publicstaticMapgetParamValue(ProceedingJoinPointjoinPoint){
Object[]paramValues=joinPoint.getArgs();
String[]paramNames=((CodeSignature)joinPoint.getSignature()).getParameterNames();
Mapparam=newHashMap<>(paramNames.length);

for(inti=0;ireturnparam;
}
}

4.Token值生成

importcom.springboot.micrometer.idempotent.RedisIdempotentStorage;
importcom.springboot.micrometer.util.IdGeneratorUtil;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RestController;

importjavax.annotation.Resource;

@RestController
@RequestMapping("/idGenerator")
publicclassIdGeneratorController{

@Resource
privateRedisIdempotentStorageredisIdempotentStorage;

@RequestMapping("/getIdGeneratorToken")
publicStringgetIdGeneratorToken(){
StringgenerateId=IdGeneratorUtil.generateId();
redisIdempotentStorage.save(generateId);
returngenerateId;
}

}
publicinterfaceIdempotentStorage{

voidsave(StringidempotentId);

booleandelete(StringidempotentId);
}
importorg.springframework.data.redis.core.RedisTemplate;
importorg.springframework.stereotype.Component;

importjavax.annotation.Resource;
importjava.io.Serializable;
importjava.util.concurrent.TimeUnit;

@Component
publicclassRedisIdempotentStorageimplementsIdempotentStorage{

@Resource
privateRedisTemplateredisTemplate;

@Override
publicvoidsave(StringidempotentId){
redisTemplate.opsForValue().set(idempotentId,idempotentId,10,TimeUnit.MINUTES);
}

@Override
publicbooleandelete(StringidempotentId){
returnredisTemplate.delete(idempotentId);
}
}
importjava.util.UUID;

publicclassIdGeneratorUtil{

publicstaticStringgenerateId(){
returnUUID.randomUUID().toString();
}

}

5. 請求示例

調(diào)用接口之前,先申請一個token,然后帶著服務(wù)端返回的token值,再去請求。

importcom.springboot.micrometer.annotation.Idempotent;
importcom.springboot.micrometer.entity.Order;
importcom.springboot.micrometer.entity.RequestData;
importorg.springframework.web.bind.annotation.RequestBody;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
publicclassOrderController{

@RequestMapping("/saveOrder")
@Idempotent(name="requestData",type=RequestData.class,field="token")
publicStringsaveOrder(@RequestBodyRequestDatarequestData){
return"success";
}

}

請求獲取token值。

533d7310-43cc-11ee-a2ef-92fbcf53809c.png

帶著token值,第一次請求成功。

53540ddc-43cc-11ee-a2ef-92fbcf53809c.png

第二次請求失敗。

53676bde-43cc-11ee-a2ef-92fbcf53809c.png


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8355

    瀏覽量

    150517
  • RPC
    RPC
    +關(guān)注

    關(guān)注

    0

    文章

    110

    瀏覽量

    11483
  • 管理系統(tǒng)
    +關(guān)注

    關(guān)注

    1

    文章

    2327

    瀏覽量

    35708

原文標題:一個注解,優(yōu)雅的實現(xiàn)接口冪等性!

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    離線計算中的和DataWorks中的相關(guān)事項

    多次相同的消息,針對同筆交易的付款也不應(yīng)該在重試過程中扣多次錢。曾見過案例,有對于
    發(fā)表于 02-27 13:24

    在高并發(fā)下怎么保證接口?

    前言 接口性問題,對于開發(fā)人員來說,是跟語言無關(guān)的公共問題。本文分享了些解決這類問題非
    的頭像 發(fā)表于 05-14 10:23 ?1752次閱讀
    在高并發(fā)下怎么保證<b class='flag-5'>接口</b>的<b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>?

    注解定義Bean及開發(fā)

    注解本質(zhì)是繼承了Annotation 的特殊接口,其具體實現(xiàn)類是Java 運行時生成的動態(tài)代理類。
    發(fā)表于 08-02 10:26 ?417次閱讀

    什么是?關(guān)于接口的解決方案

    這里的樂觀鎖指的是用樂觀鎖的原理去實現(xiàn),為數(shù)據(jù)字段增加version字段,當(dāng)數(shù)據(jù)需要更新時,先去數(shù)據(jù)庫里獲取此時的version版本號
    發(fā)表于 10-09 10:19 ?1878次閱讀

    分析解決)的方法

    這個概念,是數(shù)學(xué)上的概念,即:f……(f(f(x))) = f(x)。用在計算機領(lǐng)域,指的是系統(tǒng)里的接口或方法對外的
    的頭像 發(fā)表于 10-14 10:08 ?885次閱讀

    Spring Boot實現(xiàn)接口的4種方案

    數(shù)學(xué)與計算機學(xué)概念,在數(shù)學(xué)中某元運算為
    的頭像 發(fā)表于 11-08 10:21 ?944次閱讀

    如何設(shè)計優(yōu)雅的API接口

    種是API接口提供方給出AK/SK兩值,雙方約定用SK作為簽名中的密鑰。AK接口調(diào)用方作為header中的accessKey傳遞給API接口
    的頭像 發(fā)表于 12-20 14:23 ?1531次閱讀

    什么是?實現(xiàn)原理

    在編程中操作的特點是其任意多次執(zhí)行所產(chǎn)生的影響均與次執(zhí)行的影響相同。
    發(fā)表于 01-05 10:40 ?5845次閱讀

    給定接口,要用戶自定義動態(tài)實現(xiàn)并上傳熱部署

    考慮到用戶實現(xiàn)接口的兩種方式,使用spring上下文管理的方式,或者不依賴spring管理的方式,這里稱它們?yōu)?b class='flag-5'>注解方式和反射方式。calculate方法對應(yīng)注解方式,add方法對應(yīng)反射
    的頭像 發(fā)表于 01-06 14:14 ?500次閱讀

    如何實現(xiàn)注解進行數(shù)據(jù)脫敏

    、測試 后記 ? 本文主要分享什么是數(shù)據(jù)脫敏,如何優(yōu)雅的在項目中運用注解實現(xiàn)數(shù)據(jù)脫敏,為項目進行賦能。希望能給你們帶來幫助。 什么是數(shù)據(jù)
    的頭像 發(fā)表于 06-14 09:37 ?919次閱讀
    如何<b class='flag-5'>實現(xiàn)</b><b class='flag-5'>一</b><b class='flag-5'>個</b><b class='flag-5'>注解</b>進行數(shù)據(jù)脫敏

    基于接口解決方案

    接口是指無論調(diào)用接口的次數(shù)是次還是多次,對于同
    的頭像 發(fā)表于 09-30 16:27 ?388次閱讀
    基于<b class='flag-5'>接口</b><b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>解決方案

    和非請求的些定義和分析

    最近在做項目的過程中,有需求是在客戶端 HTTP 請求失敗后,增加重試機制,然后我就翻了些有關(guān)“重試”的庫,找到
    的頭像 發(fā)表于 10-17 10:50 ?689次閱讀

    接口統(tǒng)異常優(yōu)雅處理介紹及實戰(zhàn)

    Spring在3.2版本增加了注解@ControllerAdvice,可以與@ExceptionHandler、@InitBinder、@ModelAttribute
    的頭像 發(fā)表于 10-22 16:01 ?668次閱讀
    <b class='flag-5'>接口</b>統(tǒng)<b class='flag-5'>一</b>異常<b class='flag-5'>優(yōu)雅</b>處理介紹及實戰(zhàn)

    為什么要實現(xiàn)校驗 如何實現(xiàn)接口校驗

    前端重復(fù)提交表單:在填寫些表格時候,用戶填寫完成提交,很多時候會因網(wǎng)絡(luò)波動沒有及時對用戶做出提交成功響應(yīng),致使用戶認為沒有成功提交,然后直點提交按鈕,這時就會發(fā)生重復(fù)提交表單請求。
    的頭像 發(fā)表于 02-20 14:14 ?1080次閱讀

    探索LabVIEW編程接口原理與實踐

    原來是數(shù)學(xué)上的概念,在編程領(lǐng)域可以理解為:多次請求某一個資源或執(zhí)行某一個操作時應(yīng)該具有唯一性
    的頭像 發(fā)表于 02-29 10:24 ?523次閱讀
    探索LabVIEW編程<b class='flag-5'>接口</b><b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>原理與實踐