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

news2024/11/24 6:52:37

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

  • 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{

    @Resource
    private 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.coloradmin.cn/o/1522605.html

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

相关文章

无人机助力智慧农田除草新模式,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建无人机航拍场景下的农田杂草检测识别系统

科技发展到今天&#xff0c;无人机喷洒药物已经不是一件新鲜事情了&#xff0c;在很多高危的工作领域中&#xff0c;比如高空电力设备除冰&#xff0c;电力设备部件传送更换等等&#xff0c;无人机都可以扮演非常出色的作用&#xff0c;前面回到老家一段时间&#xff0c;最近正…

Linux系统——Session ID(负载均衡如何保持会话)

目录 一、实验环境搭建 二、部署Nginx代理服务器配置 三、部署后端真是服务器Tomcat配置 四、配置Tomcat的Session ID会话保持 五、测试 此次实验是Tomcat后端服务器如何做Session ID会话保持 一、实验环境搭建 [rootlocalhost ~]#systemctl stop firewalld [rootlocalho…

CodeTop day3

class Solution {public int[] sortArray(int[] nums) {//这种方法超时【快速排序】for (int i0;i<nums.length-1;i){int minIndex i;//假设当前开始下标为最小元素下标for (int ji1;j<nums.length;j){//从i到nums。length-1区间里找到最小值下标if (nums[j]<nums[mi…

腾讯云图形验证码的PHP示例

需要准备的 1.API密钥 SecretId 及 SecretKey 两部分&#xff0c; SecretId 用于标识 API 调用者的身份&#xff0c; SecretKey 用于加密签名字符串和服务器端验证签名字符串的密钥。 前往API密钥管理页面&#xff0c;即可进行获取 https://console.cloud.tencent.com/cam/ca…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:UIExtensionComponent (系统接口))

UIExtensionComponent用于支持在本页面内嵌入其他应用提供的UI。展示的内容在另外一个进程中运行&#xff0c;本应用并不参与其中的布局和渲染。 通常用于有进程隔离诉求的模块化开发场景。 说明&#xff1a; 该组件从API Version 10开始支持。后续版本如有新增内容&#xff0…

Linux服务器(RedHat、CentOS系)安全相关巡检shell脚本

提示&#xff1a;巡检脚本可以使用crontab定时执行&#xff0c;人工根据执行结束时间点统一收集报告文件即可。 #!/bin/bash# Define output file current_date$(date "%Y%m%d") # Gets the current date in YYYYMMDD format echo >server_security_inspection_r…

Python基础综合案例-数据可视化

一、数据可视化 - 折线图可视化 1.1、json数据格式 """ 演示JSON数据和Python字典的相互转换 """ import json # 准备列表&#xff0c;列表内每一个元素都是字典&#xff0c;将其转换为JSON data [{"name": "张大山", &quo…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Stack)

堆叠容器&#xff0c;子组件按照顺序依次入栈&#xff0c;后一个子组件覆盖前一个子组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 接口 Stack(value?: { ali…

深度学习-基于机器学习的情绪分析研究

概要 互联网技术的迅速发展使得社交平台逐渐成为热点事件中社会情感的枢纽。社会热点事件的舆论监管的其中一个重要环节就是能够准确分析民众的社会情绪。本文旨在探索可以基于文本大数据彻底分析民众对热点事件的社会情绪的模型和方法。先是从社交平台上借助文本大数据、对数据…

实战:django项目环境搭建(pycharm,virtualBox)

django项目环境搭建 一.创建虚拟环境二.创建PyCharm远程连接 一.创建虚拟环境 需要用到的软件&#xff1a;PyCharm&#xff0c;VirtualBox虚拟机。 1.打开虚拟机终端&#xff0c;创建新的虚拟环境 Book。 2.在虚拟环境中创建新的文件夹 library&#xff0c;cd命令进入该文件…

抖去推无人直播+矩阵托管+AI文案撰写一体化工具如何开发搭建

一、 开发和搭建抖去推无人直播矩阵托管AI文案撰写一体化工具需要以下步骤&#xff1a; 确定功能需求&#xff1a;确定抖去推无人直播、矩阵托管和AI文案撰写的具体功能需求&#xff0c;如直播推流、直播管理、托管服务、AI文案生成等。 技术选型&#xff1a;选择适合开发该工…

OpenCV 环境变量参考

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a; OpenCV4.9.0配置选项参考 下一篇&#xff1a;OpenCV4.9.0配置选项参考 引言&#xff1a; OpenCV是一个广泛使用的图像和视频处理开源库&#xff0c;拥有丰富的图像算法和函…

基于opencv的图像处理系统的设计与实现

概要 随着计算机技术的飞速发展&#xff0c;图像技术在各领域的研究和应用日渐深入和广泛。opencv是近年来推出的开源、免费的计算机视觉库,利用其所包含的函数可以很方便地实现数字图像处理。本文旨在对opencv进行一个快速全面简介,通过介绍图像处理的相关函数&#xff0c;使读…

npm下载慢换国内镜像地址

1 设置淘宝镜像地址 npm config set registry http://registry.npm.taobao.org 2 查看当前下载地址 npm config get registry 3 其它镜像地址列表&#xff1a; 1. 官方镜像&#xff1a;https://registry.npmjs.org/ 2. 淘宝镜像&#xff1a;https://registry.npm.taobao.o…

浏览器如何进行静态资源缓存?—— 强缓存 协商缓存

在平时使用浏览器排查问题的过程中&#xff0c;我们有时会看到浏览器网络请求中出现304状态码&#xff0c;那么是什么情况下出现304呢&#xff1f;下面是关于这一现象的解释&#xff1a; 浏览器如何进行静态资源缓存&#xff1f;—— 强缓存 & 协商缓存 状态码 304浏览器如…

Discuz! X3.5精品模板下载网站模板utf-8

适合做模板下载网站&#xff0c;模板涵盖广告设计/电商设计/海报/名片/字体/展板/X展架,下载即用,精品优质,海量免费模板网下载下载,专业模板素材网站,让设计变得更简单! 下载地址&#xff1a;Discuz! X3.5精品模板下载网站模板.zip 截图&#xff1a;

【C语言】字符与字符串---从入门到入土级详解

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C语言 ⚙️操作环境:Visual Studio 2022 目录 一.字符类型和字符数组&#xff08;串&#xff09;简介 1.ASCII 2.定义&#xff0c;初始化&#xff0c;使用 1>字符的定义及初始化 2>字符串的定义及初始化 二.…

Qt教程 — 3.3 深入了解Qt 控件:Input Widgets部件(2)

目录 1 Input Widgets简介 2 如何使用Input Widgets部件 2.1 QSpinBox组件-窗口背景不透明调节器 2.2 DoubleSpinBox 组件-来调节程序窗口的整体大小 2.3 QTimeEdit、QDateEdit、QDateTimeEdit组件-编辑日期和时间的小部件 Input Widgets部件部件较多&#xff0c;将分为三…

JavaWeb笔记 --- 四、HTMlCSS

四、HTMl&CSS HTML入门 基本标签 图片、音频、视频标签 尺寸单位 px&#xff1a;像素 百分比 超链接标签 列表标签 表格标签 布局标签 表单标签 CSS导入方式 CSS选择器

Lua中文语言编程源码-第一节,更改llex.c词法分析器模块, 使Lua支持中文关键词。

源码已经更新在CSDN的码库里&#xff1a; git clone https://gitcode.com/funsion/CLua.git 在src文件夹下的llex.c&#xff0c;是Lua的词法分析器模块。 增加中文保留字标识符列表&#xff0c;保留英文保留字标识符列表。 搜索“ORDER RESERVED”&#xff0c;将原始代码 …