接口幂等性问题和常见解决方案

接口幂等性问题和常见解决方案

  • 1.什么是接口幂等性问题
    • 1.1 会产生接口幂等性的问题
    • 1.2 解决思路
  • 2.接口幂等性的解决方案
    • 2.1 唯一索引解决方案
    • 2.2 乐观锁解决方案
    • 2.3 分布式锁解决方案
    • 2.4 Token解决方案(最优方案)

1.什么是接口幂等性问题

幂等性: 用户同一操作发起的一次多次请求的结果是一致的

在增删改查4个操作中, 查询不会修改数据, 删除进行一次或者多次的产生的结果一致, 所以只需要关注修改新增操作, 修改和新增在重复提交的场景下会产生接口幂等性问题

1.1 会产生接口幂等性的问题

  • 定时任务重复执行
  • 使用了失效或超时的重试机制, 发起的重试
  • 第三方平台的接口, 因为异常导致多次异步回调
  • 中间件、应用服务根据自身特性, 也有可能进行重试
  • 使用浏览器后退按钮重复之前的操作, 导致重复提交表单
  • 网络波动等异常, 未收到反馈后发起重复请求, 页面重复刷新
  • 用户在使用的时候无意多次点击(重复操作),或者没有响应而导致多次下单或者交易。

1.2 解决思路

解决思路分为两个方向:

  • 客户端防止重复调用
  • 服务端防止重复调用

2.接口幂等性的解决方案

2.1 唯一索引解决方案

根据业务需求, 对数据表中字段设置唯一索引, 可以是单一索引, 也可以是联合索引, 防止新增时出现脏数据

例如: 新增用户数据, 具体流程:

  1. 给表中的手机号设置唯一索引
  2. 第一次请求, 插入成功
  3. 后续请求, 抛出唯一索引冲突异常(DuplicateKeyException), 插入失败

优缺点: 操作简单, 只要对字段建立唯一索引即可, 但是只适用于新增操作, 而且效率不高, 基于数据库机制去防止重复新增, 相当于把压力都给到了数据库, 在高并发情况下会出现性能问题

2.2 乐观锁解决方案

根据业务需求, 给数据表添加一个版本字段(version), 执行更新操作时, 比较版本号. 如果版本号相同, 则可以更新成功, 并在更新时增加版本号, 如果版本号不同, 则更新失败

例如: 更新账户余额, 具体流程:

  1. 给表中添加版本号字段(version), 默认为0
  2. 第一次请求, 开启事务, 将id为1的用户的账户余额+10
start transaction;
update account set money = money + 10, version = version + 1 where id = 1 and version = 1;
  1. 第二次请求, 开启事务, 将id为1的用户的账户余额更新-20
start transaction;
update account set money = money - 20, version = version + 1 where id = 1 and version = 1;
  1. 第一次请求, 提交事务, 更新成功
  2. 第二次请求, 提交事务, 更新失败, 因为version = 1这个条件已经不符合了
    在这里插入图片描述

缺点:

  • 只适用于更新操作
  • 无法完全保证幂等性, 例如第一个请求已经完成并提交事务, 那么第二个请求即使是相同的请求, 仍然会修改数据

2.3 分布式锁解决方案

这里演示使用Redis + 自定义注解 + AOP解决

  1. 浏览器请求接口时, 携带一个唯一标识(前端生成, 可以是UUID或者类似的唯一标识符), 短时间内重复点击, 唯一标识相同
  2. 将唯一标识缓存到Redis中, 并设置超时时间, 例如500毫秒
  3. 第一次请求, 设置成功(setNx方法), 继续操作数据
  4. 第二次请求, 设置失败, 代表已经有线程在执行同一个请求了, 直接返回, 不进行重复操作

代码实现:

  • 自定义注解(实现更灵活的接口幂等性校验)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {/*** 过期时长(毫秒)*/long expire();
}
  • 针对添加了Idempotent注解的接口, 进行AOP
@Aspect
@Component
@Slf4j
public class IdempotentAspect{@Resourceprivate RedisTemplate<String,String> redisTemplate;@Pointcut("@annotation(com.itheima.annotation.Idempotent)")public void execute(){}@Around("execute()")public Object around(ProceedingJoinPoint joinPoint) {HttpServletRequest request =((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();// 获取本次请求唯一标识String token = request.getHeader("token");// 获取注解对象Idempotent annotation = method.getAnnotation(Idempotent.class);// 缓存设置(setNx方法), key为唯一标识, value为随机值, 过期时间为注解的设置, 单位是毫秒Boolean b = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", nnotation.expireMillis(), TimeUnit.MILLISECONDS);if (b != null && b) {// 放行, 执行业务方法Object obj = joinPoint.proceed();// 删除缓存redisTemplate.opsForValue().delete(redisKey);return obj;}else {// 友好提示 throw new RuntimeException("您操作的太快,请稍后再试");;}}
}

缺点:

  • 浏览器快速点击, 产生了两次请求, 第一次请求先到服务器, 因为某些原因, 第二次请求达到服务器时, 第一次请求已经执行完毕并释放了锁, 此时第二次请求仍然可以加锁成功, 并执行业务逻辑, 这种情况下幂等性失效

客户端连续发起多次请求,这多次请求同时到达服务端,此时开始争抢锁,谁抢到锁谁就执行,其他没有抢到锁的请求都统统不执行。这种情况能保证幂等性。

2.4 Token解决方案(最优方案)

解决幂等性的思路: 同一个操作的一个请求或多个请求, 只执行第一次请求, 后续的都不执行

  1. 后端提供一个返回Token的接口, 后端会将Token写入缓存, 并响应给前端(这个token等于是一个一次性的钥匙, 例如二维码)
  2. 浏览器携带Token请求目标接口, 在拦截器中校验Redis中是否有这个Token(等于开锁)
  3. 校验通过, 删除缓存(一次性的钥匙销毁), 并执行业务逻辑
  4. 此时如果重复请求, 依旧携带这个Token访问, 但是因为Redis找不到这个钥匙了, 所以访问失败(因为一次性的钥匙已经被使用了)

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

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

相关文章

JMeter 面试题及答案整理,最新面试题

JMeter中如何进行性能测试的规划和设计&#xff1f; 进行JMeter性能测试的规划和设计主要遵循以下几个步骤&#xff1a; 1、确定测试目标&#xff1a; 明确性能测试的目的和目标&#xff0c;比如确定要测试的系统性能指标&#xff08;如响应时间、吞吐量、并发用户数等&#…

html编辑器

HTML 编辑器推荐 html可以使用记事本编辑 但是更建议使用专业的 HTML 编辑器来编辑 HTML&#xff0c;我在这里给大家推荐几款常用的编辑器&#xff1a; VS Code&#xff1a;https://code.visualstudio.com/WebStorm: https://www.jetbrains.com/webstorm/Notepad: https://no…

Golang协程详解

一.协程的引入 1.通过案例文章引入并发,协程概念 见:[go学习笔记.第十四章.协程和管道] 1.协程的引入,调度模型&#xff0c;协程资源竞争问题 通过上面文章可以总结出Go并发编程原理: 在一个处理进程中通过关键字 go 启用多个协程&#xff0c;然后在不同的协程中完成不同的子任…

紫色星空月亮404网页模板源码

紫色星空月亮404网页模板源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面 源码下载 紫色星空月亮404网页模板源码

【网络】负载均衡

OSI模型每一层的负载均衡 在OSI模型中&#xff0c;每一层的负载均衡具体如下&#xff1a; 1. 第二层&#xff08;数据链路层&#xff09;&#xff1a;数据链路层的负载均衡通常涉及对MAC地址的操作。在这一层&#xff0c;可以使用虚拟MAC地址技术&#xff0c;外部设备对虚拟MA…

专升本 C语言笔记-07 逗号运算符

1.逗号表达式的用法 就是用逗号隔开的多个表达式。逗号表达式&#xff0c;从左向右依次执行。 2.逗号表达式的特性 2.1.当没有括号时&#xff0c;第一个表达式为整个表达式的值。 代码 int x 3,y 5,a 0; a x,y; printf("a %d",a); 说明:因为逗号优先级最低,会…

火车订票管理系统|基于springboot框架+ Mysql+Java+B/S结构的火车订票管理系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 管理员功能登录前台功能效果图 用户功能模块 系统功能设计 数据库E-R图设计 lunwen…

独立IP的线路对TikTok直播的重要性

在当今的数字时代&#xff0c;TikTok已经成为了一个全球性的社交媒体平台&#xff0c;其直播功能也越来越受欢迎。然而&#xff0c;要在TikTok上进行直播&#xff0c;一个关键的因素是具有稳定而可靠的网络连接。而拥有独立IP的线路则在这一过程中扮演了至关重要的角色。本文将…

网络编程套接字——实现简单的UDP网络程序

目录 1、预备知识 1.1、认识端口号 1.2、端口号 vs 进程pid 1.3、认识TCP协议 1.4、认识UDP协议 1.5、网络字节序 2、socket编程接口 2.1、socket常见API 2.2、sockaddr结构 3、实现一个简易的UDP服务器和客户端通信 log.hpp UdpServer.hpp UdpClient.cc Main.cc…

C#,图论与图算法,图(Graph)的数据结构设计与源代码

因为后面即将发布的大量有关“图”的算法与源代码都需要用到下面的这些基础数据&#xff0c;为避免大家去下载&#xff0c;特意先发布于此。 一、图&#xff08;Graph&#xff09;的基础知识 图&#xff08;Graph&#xff09;是一组对象的图示&#xff0c;其中一些对象对通过链…

政安晨:【深度学习处理实践】(九)—— Transformer架构

咱们接着这个系列的上一篇文章继续&#xff1a; 政安晨&#xff1a;【深度学习处理实践】&#xff08;八&#xff09;—— 表示单词组的两种方法&#xff1a;集合和序列https://blog.csdn.net/snowdenkeke/article/details/136762323 Transformer是一种架构&#xff0c;用于在…

hot100 -- 矩阵

&#x1f442; Peter Pan - kidult. - 单曲 - 网易云音乐 &#x1f442; Bibliothque&#xff08;图书馆&#xff09; - Jasing Rye - 单曲 - 网易云音乐 目录 &#x1f33c;前言 &#x1f33c;二分模板 &#x1f382;矩阵置零 AC 标记数组 AC 标记变量 &#x1f6a9;…