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

目录

一、什么是幂等性?

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

三、为什么需要幂等

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.mzph.cn/news/732224.shtml

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

相关文章

计算机网络——计算机网络的性能

计算机网络——计算机网络的性能 速率带宽吞吐量时延时延宽带积往返时间RTT利用率信道利用率网络利用率 我们今天来看看计算机网络的性能。 速率 速率这个很简单&#xff0c;就是数据的传送速率&#xff0c;也称为数据率&#xff0c;或者比特率&#xff0c;单位为bit/s&#…

神经网络 梯度与神经元参数w、b关系;梯度与导数关系

参考&#xff1a;https://blog.csdn.net/weixin_44259490/article/details/90295146 视频&#xff1a;https://www.bilibili.com/video/BV1a14y167vh 概念 梯度与w的关系可以用梯度下降公式来表示&#xff1a;ww−α ∂ c o s t ∂ w \frac{\partial cost}{\partial w} ∂w∂…

vs创建asp.net core webapi发布到ISS服务器

打开服务器创建test123文件夹&#xff0c;并设置共享。 ISS配置信息&#xff1a; 邮件网站&#xff0c;添加网站 webapi asp.net core发布到ISS服务器网页无法打开解决方法 点击ISS Express测试&#xff0c;可以成功打开网页。 点击生成&#xff0c;发布到服务器 找到服务器IP…

idm对比aria2哪个好 aria2和idm哪个快 Aria2和IDM的原理

一、idm对比aria2哪个好 下面对aria2和idm进行对比&#xff0c;看看哪款更好。 idm: 优势&#xff1a; 1&#xff09;可将下载速度提升5倍以上&#xff1b; 2&#xff09;界面友好&#xff0c;操作简便&#xff1b; 3&#xff09;支持多个主流的浏览器&#xff1b; 4&am…

基于Vue的娱讯移动端APP前端设计与实现

目 录 摘 要 Abstract 引 言 1绪论 1.1课题背景及目的 1.1.1移动端APP发展简介 3 1.1.2移动端APP的优势 3 1.2前端开发相关技术 1.2.1前端开发工具介绍 3 1.2.2 前端开发相关技术介绍 4 1.3本章小结 2系统分析 2.1功能需求分析 2.2系统工作流程 2.3本章小结 3系统设…

【论文阅读】Segment Anything论文梳理

Abstract 我们介绍了Segment Anything&#xff08;SA&#xff09;项目&#xff1a;新的图像分割任务、模型和数据集。高效的数据循环采集&#xff0c;使我们建立了迄今为止最大的分割数据集&#xff0c;在1100万张图像中&#xff0c;共超过10亿个掩码。 该模型被设计和训练为可…

一例APC注入型病毒分析

概述 这个病毒通过可移动存储介质传播&#xff0c;使用了应用层APC注入和dga域名技术&#xff0c;整个执行过程分为4个阶段&#xff0c;首先从资源节中解密出一段shellcode和一个PE&#xff0c;执行shellcode&#xff0c;创建一个同名的傀儡进程&#xff0c;将解密出来的PE注入…

2024蓝桥杯每日一题(归并排序)

一、第一题&#xff1a;火柴排队 解题思路&#xff1a;归并排序 重点在于想清楚是对哪个数组进行归并排序求逆序对 【Python程序代码】 from math import * n int(input()) a list(map(int,input().split())) b list(map(int,input().split())) na,nb [],[] for …

【嵌入式】字体极限瘦身术:Fontmin在嵌入式UI中的魔法应用(附3500常用汉字)

1. 概述 在嵌入式系统的用户界面&#xff08;UI&#xff09;设计中&#xff0c;字体的选择和优化至关重要。一个恰当的字体不仅能够提升用户体验&#xff0c;还能彰显产品特色。然而&#xff0c;由于嵌入式设备常常受限于存储空间和处理能力&#xff0c;大型字体文件可能成为性…

LeetCode的使用方法

LeetCode的使用方法 一、LeetCode是什么&#xff1f;1.LeetCode简介2.LeetCode官网 二、LeetCode的使用方法1.注册账号2.力扣社区力扣编辑器 2.1 讨论发起讨论参与讨论关注讨论 2.2 文章撰写文章关注文章 3.力扣面试官版测评面试招聘竞赛 4.力扣学习LeetBook 书架我的阅读猜您喜…

支付宝开放平台证书验签生成签名接入方式的操作流程之公钥证书,密钥证书的生成

#小李子9479# 调用支付宝接口的安全验证方式均使用sign_type为RSA2的方式&#xff0c;有两种 1。密钥模式&#xff1a;应用公钥、应用私钥、平台公钥生成签名和验签方式 2。证书模式&#xff1a;支付宝根证书、支付宝公钥证书、应用公钥证书、应用私钥&#xff0c;采用RSA20…

【2024.3.8练习】[2015 国 AC] 穿越雷区

题目描述 题目分析 最短步数问题&#xff0c;采用BFS算法即可。 我的代码 #include <iostream> #include <algorithm> #include <queue> #include <cmath> using namespace std; int n; int ans; int flag; const int max_n 102; char map[max_n][m…

Qt初识 - 编写Hello World的两种方式 | 对象树

目录 一、通过图形化方式&#xff0c;在界面上创建出一个控件 二、通过代码方式&#xff0c;创建Hello World 三、Qt 内存泄漏问题 (一) 对象树 一、通过图形化方式&#xff0c;在界面上创建出一个控件 创建项目后&#xff0c;打开双击forms文件夹中的ui文件&#xff0c;可…

【java基础】异常处理机制

目录 1、异常体系介绍 1.1、异常是什么? 1.2、运行时异常和编译时异常的区别? 2、异常的用法 2.1、捕获异常 2.2、异常中的常见方法 2.3、抛出异常 2.4、自定义异常 1、异常体系介绍 1.1、异常是什么? java异常是指在程序运行时可能出现的一些错误&#xff0c;如&am…

责任链模式(Chain of Responsibility Pattern)

责任链模式 说明 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;属于行为型模式&#xff0c;它是指使多个对象都有机会处理请求&#xff0c;将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处理它为止。从而避免请求…

IntelliJ IDEA自定义关闭当前文件的快捷方式

前言 idea中关闭当前标签页的默认快捷键是CtrlF4,这个组合键在键盘上操作起来很是不方便&#xff0c;我们可以在设置中自定义自己习惯的快捷方式。 自定义步骤 要在 IntelliJ IDEA 中将关闭当前文件的快捷方式设置为 Alt Q&#xff0c;请按照以下步骤操作&#xff1a;打开 …

github Commits must have verified signatures

1.首先确认是否有权限&#xff0c;如有权限的情况下那就是配置有问题了 我的情况是&#xff0c;能拉取代码&#xff0c;提交的时候出现这种情况&#xff1a;Commits must have verified signatures 这里是生成证书&#xff0c;如果已经生成过的&#xff0c;就不用生成了 ssh…

ARM/Linux嵌入式面经(一):海康威视

海康威视 1.函数指针和指针函数区别 1.定义的差异 函数指针&#xff1a;函数指针的定义涉及到函数的地址。例如&#xff0c;定义一个指向函数的指针 int (*fp)(int)&#xff0c;这里 fp 是一个指针&#xff0c;它指向一个接受一个整数参数并返回整数的函数。 指针函数&#…

《C缺陷和陷阱》-笔记(2)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 一、理解函数声明 1.(*(void(*)( ))0)( ); 2.signal 函数接受两个参数&#xff1a; 3.使用typedef 简化函数声明&#xff1a; 二、运算符的优先级…

【项目】Boost 搜索引擎

文章目录 1.背景2.宏观原理3.相关技术与开发环境4. 实现原理1.下载2.加载与解析文件2.1获取指定目录下的所有网页文件2.2. 获取网页文件中的关键信息2.3. 对读取文件进行保存 3.索引3.1正排与倒排3.2获取正排和倒排索引3.3建立索引3.3.1正排索引3.3.2倒排索引 4.搜索4.1 初始化…