Aop注解+Redis解决SpringBoot接口幂等性(源码自取)

news/2024/4/20 19:53:23/文章来源:https://blog.csdn.net/m0_51406695/article/details/136559353

目录

一、什么是幂等性?

二、哪些请求天生就是幂等的?

三、为什么需要幂等

1.超时重试

2.异步回调

3.消息队列

四、实现幂等的关键因素

关键因素1

关键因素2

五、引入幂等性后对系统的影响

六、Restful API 接口的幂等性

实战Aop注解+redis解决幂等性

reidis解决主要流程

添加所需依赖

application简单配置

封装自定义注解 

 封装统一请求入参对象

Header

Order

 ​编辑RequestData

定义Aop切面 

生成token

​编辑 启动测试

 简单总结

源码 


一、什么是幂等性?


简单来说,就是对一个接口执行重复的多次请求,与一次请求所产生的结果是相同的,听起来非常容易理解,但要真正的在系统中要始终保持这个目标,是需要很严谨的设计的,在实际的生产环境下,我们应该保证任何接口都是幂等的

在HTTP/1.1中,对幂等性进行了定义。它描述了一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外),即第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。这里的副作用是不会对结果产生破坏或者产生不可预料的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

二、哪些请求天生就是幂等的?


首先,我们要知道查询类的请求一般都是天然幂等的,除此之外,删除请求在大多数情况下也是幂等的,但是ABA场景下除外。

举一个简单的例子
比如,先请求了一次删除A的操作,但由于响应超时,又自动请求了一次删除A的操作,如果在两次请求之间,又插入了一次A,而实际上新插入的这一次A,是不应该被删除的,这就是ABA问题,不过,在大多数业务场景中,ABA问题都是可以忽略的。

除了查询和删除之外,还有更新操作,同样的更新操作在大多数场景下也是天然幂等的,其例外是也会存在ABA的问题,更重要的是,比如执行update table set a = a + 1 where v = 1这样的更新就非幂等了。

最后,就还剩插入了,插入大多数情况下都是非幂等的,除非是利用数据库唯一索引来保证数据不会重复产生。

在接口调用时一般情况下都能正常返回信息不会重复提交,不过在遇见以下情况时可以就会出现问题,如:

  • 前端重复提交表单:在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
  • 用户恶意进行刷单:例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。
  • 接口超时重复提交:很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
  • 消息进行重复消费:当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。

使用幂等性最大的优势在于使接口保证任何幂等性操作,免去因重试等造成系统产生的未知的问题。

三、为什么需要幂等


1.超时重试


当发起一次RPC请求时,难免会因为网络不稳定而导致请求失败,一般遇到这样的问题我们希望能够重新请求一次,正常情况下没有问题,但有时请求实际上已经发出去了,只是在请求响应时网络异常或者超时,此时,请求方如果再重新发起一次请求,那被请求方就需要保证幂等了。

2.异步回调


异步回调是提升系统接口吞吐量的一种常用方式,很明显,此类接口一定是需要保证幂等性的。

3.消息队列


现在常用的消息队列框架,比如:Kafka、RocketMQ、RabbitMQ在消息传递时都会采取At least once原则(也就是至少一次原则,在消息传递时,不允许丢消息,但是允许有重复的消息),既然消息队列不保证不会出现重复的消息,那消费者自然要保证处理逻辑的幂等性了。

四、实现幂等的关键因素


关键因素1

幂等唯一标识,可以叫它幂等号或者幂等令牌或者全局ID,总之就是客户端与服务端一次请求时的唯一标识,一般情况下由客户端来生成,也可以让第三方来统一分配。

关键因素2

有了唯一标识以后,服务端只需要确保这个唯一标识只被使用一次即可,一种常见的方式就是利用数据库的唯一索引

五、引入幂等性后对系统的影响

幂等性是为了简化客户端逻辑处理,能放置重复提交等操作,但却增加了服务端的逻辑复杂性和成本,其主要是:

  • 把并行执行的功能改为串行执行,降低了执行效率。
  • 增加了额外控制幂等的业务逻辑,复杂化了业务功能;

所以在使用时候需要考虑是否引入幂等性的必要性,根据实际业务场景具体分析,除了业务上的特殊要求外,一般情况下不需要引入的接口幂等性。

六、Restful API 接口的幂等性

现在流行的 Restful 推荐的几种 HTTP 接口方法中,分别存在幂等行与不能保证幂等的方法,如下:

  • √ 满足幂等
  • x 不满足幂等
  • - 可能满足也可能不满足幂等,根据实际业务逻辑有关

实战Aop注解+redis解决幂等性

reidis解决主要流程

  • ① 服务端提供获取 Token 的接口,该 Token 可以是一个序列号,也可以是一个分布式 ID 或者 UUID 串。
  • ② 客户端调用接口获取 Token,这时候服务端会生成一个 Token 串。
  • ③ 然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。
  • ④ 将 Token 返回到客户端,客户端拿到后应存到表单隐藏域中。
  • ⑤ 客户端在执行提交表单时,把 Token 存入到 Headers 中,执行业务请求带上该 Headers。
  • ⑥ 服务端接收到请求后从 Headers 中拿到 Token,然后根据 Token 到 Redis 中查找该 key 是否存在。
  • ⑦ 服务端根据 Redis 中是否存该 key 进行判断,如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。

注意,在并发情况下,执行 Redis 查找数据与删除需要保证原子性,否则很可能在并发下无法保证幂等性。其实现方法可以使用分布式锁或者使用 Lua 表达式来注销查询与删除操作。

添加所需依赖

 <dependencies><dependency><groupId>io.micrometer</groupId><artifactId>micrometer-core</artifactId><version>1.8.5</version> <!-- 替换成你需要的版本 --></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version></dependency><!--        web开发场景启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--        redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.3</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

application简单配置

封装自定义注解 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {/*** 参数名,表示将从哪个参数中获取属性值。* 获取到的属性值将作为KEY。** @return*/String name() default "";/*** 属性,表示将获取哪个属性的值。** @return*/String field() default "";/*** 参数类型** @return*/Class type();}

 封装统一请求入参对象

Order
 RequestData

定义Aop切面 

import com.example.redisidempotence.annotation.Idempotent;
import com.example.redisidempotence.compent.RedisIdempotentStorage;
import com.example.redisidempotence.vo.RequestData;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;@Aspect
@Component
public class IdempotentAspect {@Resourceprivate RedisIdempotentStorage redisIdempotentStorage;@Pointcut("@annotation(com.example.redisidempotence.annotation.Idempotent)")public void idempotent() {}@Around("idempotent()")public Object methodAround(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Idempotent idempotent = method.getAnnotation(Idempotent.class);String field = idempotent.field();String name = idempotent.name();Class clazzType = idempotent.type();String token = "";Object object = clazzType.newInstance();Map<String, Object> paramValue = AopUtils.getParamValue(joinPoint);if (object instanceof RequestData) {RequestData idempotentEntity = (RequestData) paramValue.get(name);token = String.valueOf(AopUtils.getFieldValue(idempotentEntity.getHeader(), field));}if (redisIdempotentStorage.delete(token)) {return joinPoint.proceed();}if(token.isEmpty()){return "token校验失败,token不可为空";}return "不能重复请求";}
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class AopUtils {public static Object getFieldValue(Object obj, String name) throws Exception {Field[] fields = obj.getClass().getDeclaredFields();Object object = null;for (Field field : fields) {field.setAccessible(true);if (field.getName().toUpperCase().equals(name.toUpperCase())) {object = field.get(obj);break;}}return object;}public static Map<String, Object> getParamValue(ProceedingJoinPoint joinPoint) {Object[] paramValues = joinPoint.getArgs();String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();Map<String, Object> param = new HashMap<>(paramNames.length);for (int i = 0; i < paramNames.length; i++) {param.put(paramNames[i], paramValues[i]);}return param;}
}

生成token

 定义redis服务接口及实现方法

 生成token接口

模拟业务请求controller

 启动测试

首先拿取到token

将拿取到的token当做header中token传入到模拟的业务请求中

 

可以看到,将拿取到的token放在模拟真实请求的业务中时,只有第一次请求返回成功,随后的请求都返回"不能重复请求",或者token传空时也返回了相应的token校验信息,从而实现了幂等性

 简单总结

幂等性是开发当中很常见也很重要的一个需求,尤其是支付、订单等与金钱挂钩的服务,保证接口幂等性尤其重要。在实际开发中,我们需要针对不同的业务场景我们需要灵活的选择幂等性的实现方式:

  • 对于下单等存在唯一主键的,可以使用“唯一主键方案”的方式实现。
  • 对于更新订单状态等相关的更新场景操作,使用“乐观锁方案”实现更为简单。
  • 对于上下游这种,下游请求上游,上游服务可以使用“下游传递唯一序列号方案”更为合理。
  • 类似于前端重复提交、重复下单、没有唯一ID号的场景,可以通过 Token 与 Redis 配合的“防重 Token 方案”实现更为快捷。

以下是各种方法解决幂等性的优劣比较

源码 

幂等性源码demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_999607.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java代码审计安全篇-常见Java SQL注入

前言&#xff1a; 堕落了三个月&#xff0c;现在因为被找实习而困扰&#xff0c;着实自己能力不足&#xff0c;从今天开始 每天沉淀一点点 &#xff0c;准备秋招 加油 注意&#xff1a; 本文章参考qax的网络安全java代码审计&#xff0c;记录自己的学习过程&#xff0c;还希望…

el根据需求合并列

将 列分为 3 3 1 的格式 以下是vue代码&#xff1a; <el-table:data"dataSource":border"true":header-cell-style"{ font-weight: normal, text-align: center }":cell-style"{ text-align: center }"size"mini"style…

git分布式管理-头歌实验搭建Git服务器

一、Git服务器搭建 任务描述 虽然有提供托管代码服务的公共平台&#xff0c;但是对一部分开发团队来说&#xff0c;为了不泄露项目源代码、节省费用及为项目提供更好的安全保护&#xff0c;往往需要搭建私有Git服务器用做远程仓库。Git服务器为团队的开发者们&#xff0c;提供了…

智能驾驶规划控制理论学习08-自动驾驶控制模块(轨迹跟踪)

目录 一、基于几何的轨迹跟踪方法 1、基本思想 2、纯追踪 3、Stanly Method 二、PID控制器 三、LQR&#xff08;Linear Quadratic Regulator&#xff09; 1、基本思想 2、LQR解法 3、案例学习 基于LQR的路径跟踪 基于LQR的速度跟踪 4、MPC&#xff08;Mode…

2024AI在医疗领域中的辅助趋势与现有进展

2024 年 AI 辅助研发趋势随着人工智能技术的持续发展与突破&#xff0c;2024年AI辅助研发正成为科技界和工业界瞩目的焦点。从医药研发到汽车设计&#xff0c;从软件开发到材料科学&#xff0c;AI正逐渐渗透到研发的各个环节&#xff0c;变革着传统的研发模式。在这一背景下&am…

计算机设计大赛 深度学习验证码识别 - 机器视觉 python opencv

文章目录 0 前言1 项目简介2 验证码识别步骤2.1 灰度处理&二值化2.2 去除边框2.3 图像降噪2.4 字符切割2.5 识别 3 基于tensorflow的验证码识别3.1 数据集3.2 基于tf的神经网络训练代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x…

基于PHP的景点数据分析系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 关键理论与技术 3 1.1 框架技术 3 1.1.1 QueryList 3 1.1.2 ThinkPHP 3 1.1.3 Amaze UI 3 1.2 数据可视化技术 4 1.3 数据库技术 4 1.4 本章小结 4 2 需求分析 5 2.1 业务流程分析 5 2.2 功能需求分析 5 2.3 用例分析 7 2.4 非功能性需求…

短剧APP系统开发,2024年最具潜力的赛道

短剧从去年开始就呈现暴风式上涨&#xff0c;占据了大众的空闲时间&#xff0c;刷到短剧的观众几乎都会沉迷进去&#xff0c;让人欲罢不能。今年春节一部《我在八零年代当后妈》的短剧就突破了日充值达2000万的惊人数据&#xff0c;更加刺激了短剧制作商&#xff0c;短剧市场的…

第三篇【传奇开心果系列】Python的自动化办公库技术点案例示例:深度解读Pandas股票市场数据分析

传奇开心果博文系列 系列博文目录Python的自动化办公库技术点案例示例系列 博文目录前言一、Pandas进行股票市场数据分析常见步骤和示例代码1. 加载数据2. 数据清洗和准备3. 分析股票价格和交易量4. 财务数据分析 二、扩展思路介绍1. 技术指标分析2. 波动性分析3. 相关性分析4.…

线上问题——学习记录幂等判断失效问题分析

一、业务流程 上图是对save和saveScore两个接口的流程抽象&#xff0c;save是上传答题数据&#xff0c;saveScore则是上传答题分数&#xff0c;为保证幂等和防止并发调用&#xff0c;这两个接口都加了分布式锁&#xff08;还是两层哦&#xff09;。第一层使用的是不同的锁&…

设计模式大题做题记录

设计模式大题 09年 上半年&#xff1a; 09年下半年 10年上半年 10年下半年 11年上半年 11年下半年 12年上半年 12年下半年 13年上半年 13年下半年

【DP】蓝桥杯第十三届-费用报销

#include<iostream> #include<algorithm> #include<cstring> #include<set> #include<queue> using namespace std; const int N1010; int dp[N][5010];//dp[i][j]:选到第i个物品是否能取到价值j&#xff1b; int month[13]{0,31,28,31,30,31,30…

python+requests+pytest+allure自动化测试框架

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、核心库 requests request请求 openpyxl excel文件操作 log…

如何使用 ArcGIS Pro 制作三维地形图

伴随硬件性能的提高和软件算法的优化&#xff0c;三维地图的应用场景会越来越多&#xff0c;这里为大家介绍一下在ArcGIS Pro怎么制作三维地形图&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的DEM和影像数据&#xff0c;除了DEM和影像数据…

【鸿蒙 HarmonyOS 4.0】常用组件:List/Grid/Tabs

一、背景 列表页面&#xff1a;List组件和Grid组件&#xff1b; 页签切换&#xff1a;Tabs组件&#xff1b; 二、列表页面 在我们常用的手机应用中&#xff0c;经常会见到一些数据列表&#xff0c;如设置页面、通讯录、商品列表等。下图中两个页面都包含列表&#xff0c;“…

分库与分表--一起学习吧之架构

分库与分表是数据库架构中用于解决单一数据库或表性能瓶颈问题的两种重要手段。随着数据量的不断膨胀&#xff0c;传统的单一数据库或表可能无法满足应用的需求&#xff0c;这时就需要考虑采用分库或分表的方式来提升性能。 一、定义 分库&#xff1a;是将原本数据量大的数据…

基于H5的旅游攻略平台设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 系统开发相关技术 3 1.1框架技术 3 1.1.1 SSM框架 3 1.1.2 SpringBoot框架 3 1.1.3 Spring框架 3 1.2开发语言 3 1.2.1 HTML 3 1.2.2 JAVA 4 1.2.3 JavaScript 4 1.3数据库 4 1.4本章小结 4 2 系统分析 5 2.1 可行性分析 5 2.2 功能需求分…

开展“3·15”金融消费者权益保护教育宣传活动怎样联系媒体投稿?

在组织“315”金融消费者权益保护教育宣传活动时,传统的方式通常是银行、金融机构或其他主办单位主动联系各类媒体,包括但不限于电视台、广播电台、报纸、杂志、新闻网站、金融专业媒体、社交媒体平台等,通过邮件、电话、传真等方式提供活动新闻稿、宣传材料、现场照片等素材,请…

JavaScript实现记住用户名功能

问题描述&#xff1a;通过JavaScript实现点击复选框将用户名存储到本地中&#xff0c;再次打开页面&#xff0c;输入框中自动输入上次保存的数据。 <body><label for"">用户名</label> <input type"text1" name"" id"…

【深度学习笔记】6_6 通过时间反向传播(back-propagation through time)

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.6 通过时间反向传播 在前面两节中&#xff0c;如果不裁剪梯度&#xff0c;模型将无法正常训练。为了深刻理解这一现象&#xff0c;本…