在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

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

3天內不再提示

一個注解,優雅的實現接口冪等性!

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


一、什么是冪等性?

簡單來說,就是對一個接口執行重復的多次請求,與一次請求所產生的結果是相同的,聽起來非常容易理解,但要真正的在系統中要始終保持這個目標,是需要很嚴謹的設計的,在實際的生產環境下,我們應該保證任何接口都是冪等的,而如何正確的實現冪等,就是本文要討論的內容。

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

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

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

舉一個簡單的例子

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

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

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

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

三、為什么需要冪等

1.超時重試

當發起一次RPC請求時,難免會因為網絡不穩定而導致請求失敗,一般遇到這樣的問題我們希望能夠重新請求一次,正常情況下沒有問題,但有時請求實際上已經發出去了,只是在請求響應時網絡異常或者超時,此時,請求方如果再重新發起一次請求,那被請求方就需要保證冪等了。

2.異步回調

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

3.消息隊列

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

四、實現冪等的關鍵因素

關鍵因素1

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

關鍵因素2

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

五、注解實現冪等性

下面演示一種利用Redis來實現的方式。

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{

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

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

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

}

2.統一的請求入參對象

@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"重復請求";
}
}
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. 請求示例

調用接口之前,先申請一個token,然后帶著服務端返回的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


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 接口
    +關注

    關注

    33

    文章

    8610

    瀏覽量

    151213
  • RPC
    RPC
    +關注

    關注

    0

    文章

    111

    瀏覽量

    11537
  • 管理系統
    +關注

    關注

    1

    文章

    2507

    瀏覽量

    35933

原文標題:一個注解,優雅的實現接口冪等性!

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

收藏 人收藏

    評論

    相關推薦

    離線計算中的和DataWorks中的相關事項

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

    在高并發下怎么保證接口

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

    注解定義Bean及開發

    注解本質是繼承了Annotation 的特殊接口,其具體實現類是Java 運行時生成的動態代理類。
    發表于 08-02 10:26 ?442次閱讀

    什么是?關于接口的解決方案

    這里的樂觀鎖指的是用樂觀鎖的原理去實現,為數據字段增加version字段,當數據需要更新時,先去數據庫里獲取此時的version版本號
    發表于 10-09 10:19 ?1953次閱讀

    分析解決)的方法

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

    Spring Boot實現接口的4種方案

    數學與計算機學概念,在數學中某元運算為
    的頭像 發表于 11-08 10:21 ?1002次閱讀

    如何設計優雅的API接口

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

    什么是實現原理

    在編程中操作的特點是其任意多次執行所產生的影響均與次執行的影響相同。
    發表于 01-05 10:40 ?6135次閱讀

    給定接口,要用戶自定義動態實現并上傳熱部署

    考慮到用戶實現接口的兩種方式,使用spring上下文管理的方式,或者不依賴spring管理的方式,這里稱它們為注解方式和反射方式。calculate方法對應注解方式,add方法對應反射
    的頭像 發表于 01-06 14:14 ?561次閱讀

    如何實現注解進行數據脫敏

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

    基于接口解決方案

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

    和非請求的些定義和分析

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

    接口異常優雅處理介紹及實戰

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

    為什么要實現校驗 如何實現接口校驗

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

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

    原來是數學上的概念,在編程領域可以理解為:多次請求某一個資源或執行某一個操作時應該具有唯一性
    的頭像 發表于 02-29 10:24 ?628次閱讀
    探索LabVIEW編程<b class='flag-5'>接口</b><b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>原理與實踐
    主站蜘蛛池模板: 亚洲色图第一页| 在线视频一二三区| 午夜精品久久久久| 日本免费福利视频| 久色中文| 综合第一页| 亚洲天堂视频在线播放| 波多野结衣一级毛片| 日本在线视| 国产小视频在线看| 天天射天天干天天| 国产三级日本三级韩国三级在线观看| 河南毛片| 免费色网址| 黄色毛片大全| 97色伦人人| 天天干天天干天天插| 夜色成人| 欧美午夜精品久久久久久黑人 | 91大神在线看| 7777奇米| 6080国产午夜精品| 中文字幕一区二区三区四区五区 | 国产三级香港三级人妇| 在线观看免费高清| 97色在线视频观看香蕉| 欧美影院在线| 国产精品夜色7777青苹果| 欧美三四级片| 四虎最新网| 色婷婷综合激情| 色聚网久久综合| 三级黄色a| 国产日本三级在线播放线观看 | 曰本黄色一级| 日本黄色一级网站| 丁香花高清在线观看| 成人免费精品视频| 欧美成人性色生活片天天看| 免费看黄色片网站| 亚洲第一中文字幕|