接口保证幂等性你学废了吗?

接口幂等性定义:无论一次或多次调用某个接口,对资源产生的副作用都是一致的。

简单来说:用户由于各种原因(网络超时、前端重复点击、消息重试等)对同一个接口发了多次请求,系统只能处理一次,不能因为多次请求导致数据错误(如扣款两次、生成两个订单)。

方案一:Token 机制(适用于新增、提交类操作)

这是最经典的防重提交方案,尤其适合前端表单提交、支付下单等场景。

核心思想:客户端先获取一个服务器颁发的唯一令牌(Token),提交请求时必须带上这个Token。服务器处理请求后,使该Token失效。

流程:

  1. 获取Token:客户端在发起业务请求前,先调用一个接口获取一个全局唯一的Token(通常存于Redis,并设置较短的有效期)。
  2. 提交请求:客户端带着业务参数和这个Token发起业务请求。
  3. 校验Token:
    服务器端(通常通过AOP或Filter实现)检查Redis中是否存在该Token。
    存在:执行业务逻辑,然后删除Redis中的Token。
    不存在:说明该请求已被处理过,直接返回重复提交的错误信息。

代码示例(AOP实现):

// 1. 自定义一个幂等性注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {int expireTime() default 60; // Token有效期,秒
}// 2. 编写Token创建接口
@RestController
public class TokenController {@Autowiredprivate StringRedisTemplate redisTemplate;@GetMapping("/token")public String getToken() {String token = UUID.randomUUID().toString();redisTemplate.opsForValue().set(token, "1", Duration.ofSeconds(60)); // 存入Redis,60秒过期return token;}
}// 3. 编写AOP切面处理幂等性校验
@Aspect
@Component
public class IdempotentAspect {@Autowiredprivate StringRedisTemplate redisTemplate;@Around("@annotation(idempotent)")public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader("X-Idempotent-Token"); // 从Header中获取Tokenif (StringUtils.isEmpty(token)) {throw new RuntimeException("Token不存在");}// 核心逻辑:删除Token(原子操作)。如果删除成功,返回1,说明是第一次请求。Boolean isDeleted = redisTemplate.delete(token);if (Boolean.TRUE.equals(isDeleted)) {// 是第一次请求,放行return joinPoint.proceed();} else {// 删除失败,可能是Token已过期或被使用// 尝试获取Token,如果还能获取到值,说明是重复请求(但还没过期)。如果获取不到,说明已过期。String tokenValue = redisTemplate.opsForValue().get(token);if (tokenValue != null) {throw new IdempotentException("请勿重复操作");} else {throw new IdempotentException("操作已过期,请刷新页面后重试");}}}
}// 4. 在需要幂等性的接口上使用注解
@RestController
public class OrderController {@PostMapping("/createOrder")@Idempotent // 加上注解public String createOrder(@RequestBody Order order) {// 业务逻辑...return "订单创建成功";}
}

优点:实现简单,通用性强。
缺点:需要额外一次获取Token的请求。

方案二:基于数据库唯一索引(适用于插入操作)

核心思想:利用数据库唯一索引的排他性,防止重复数据插入。

流程:

  1. 在数据表中为一个或多个字段建立唯一索引。这个“唯一键”可以是:
    业务主键(如订单ID)
    组合字段(如:用户ID + 业务类型 + 关联ID)
  2. 插入数据时,如果唯一键重复,数据库会抛出 DuplicateKeyException。
  3. 代码中捕获这个异常,返回“请勿重复操作”的提示。

示例:防止用户重复创建订单。

  • 在 order 表为 user_id 和 order_source_id(本次请求的唯一源ID)建立联合唯一索引。
  • 每次创建订单时,如果同一个用户使用同一个源ID请求,第二次插入就会失败。

优点:实现最简单,无需额外编码,依靠数据库本身能力。
缺点:只适用于新增场景,不适用于更新、删除操作。

注意:
这个唯一id并不是数据库表的自增id,而是在发起请求前,生成一个全局唯一的业务ID,例如:order_id = 雪花算法生成的长整形ID。
将这个 order_id 随请求一起传到后端。
在数据库的 order 表上,为 order_id 字段建立一个唯一索引。
插入时,如果两个请求带着同一个 order_id 到来,第二个请求就会因为唯一索引冲突而插入失败。

方案三:悲观锁/乐观锁(适用于更新、扣减操作)

  1. 悲观锁(Pessimistic Lock)
    思想:“先取锁,再操作”。认为并发冲突一定会发生,因此在操作数据时直接将其锁住。
    实现:使用 SELECT … FOR UPDATE。
    场景:适用于写操作非常频繁,冲突概率极高的场景。要谨慎使用,容易导致性能瓶颈和死锁。
  2. 乐观锁(Optimistic Lock) - 更推荐
    思想:“先操作,再验证”。认为冲突不常发生,通过版本号(Version)或状态机来保证。
    实现:
    在表中增加一个 version 字段。
    更新数据时,将 version 作为条件:UPDATE table SET value = new_value, version = version + 1 WHERE id = #{id} AND version = #{old_version}。
  3. 检查 executeUpdate() 返回的影响行数:
    如果为 1:更新成功,是第一次请求。
    如果为 0:说明version已被其他请求修改过,是重复请求或冲突请求,操作失败。

示例(余额扣款):

UPDATE account SET balance = balance - 100,version = version + 1
WHERE user_id = 123 
AND version = 5; -- 旧的版本号
--通过在一次事务内,先执行 SELECT 语句查询出来最新的版本号再进行更新操作。

优点(乐观锁):性能高,避免数据库锁竞争。
缺点:需要修改表结构,失败后需要重试或告知客户端。

方案四:状态机约束(适用于有状态流转的业务)

核心思想: 很多业务数据都有明确的状态流转(如:订单状态:0待支付->1已支付->2已发货)。只有在特定状态下,操作才是允许的。
流程:
执行更新操作时,不仅以ID为条件,还必须加上当前状态作为条件。
如果状态不符合预期,则更新失败,说明请求无效或已处理过。

示例(支付回调接口):

-- 只有状态为“待支付”的订单才能被更新为“已支付”
UPDATE orders SET status = '已支付' WHERE order_id = '123' AND status = '待支付';

优点:业务逻辑本身自带的幂等性,无需额外组件。
缺点:仅适用于有状态变化的业务。

总结与选择

方案适用场景优点缺点
Token 机制通用,尤其前端提交、创建操作通用性强,可靠性高需额外接口,多一次交互
唯一索引数据插入类操作实现最简单,绝对可靠仅限插入操作
乐观锁数据更新、扣减库存类操作性能好,避免锁竞争需修改表结构,增加version字段
状态机有明确状态流转的业务(订单、流程)天然幂等,符合业务逻辑局限性较强
悲观锁极高并发写场景(少用)保证强一致性性能差,易死锁

在金融项目中如何选择?
薪酬计算触发接口:可用 Token机制 或基于业务ID的唯一索引(如 calculation_id),防止重复触发计算。
奖金发放、余额扣减接口:乐观锁是最佳选择,保证金额不会重复扣减。
订单、流程状态变更:状态机约束是必须的,例如“只有未支付的订单才能支付”。
最佳实践: 通常会将多种方案组合使用。例如,先用 Token 机制 防重,在业务逻辑内再用 乐观锁 或 状态机 做最终保障,形成双保险。

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

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

相关文章

入行FPGA选择国企、私企还是外企?

不少人想要转行FPGA,但不知道该如何选择公司?下面就来为大家盘点一下FPGA大厂的薪资和工作情况,欢迎大家在评论区补充。一、老牌巨头在 FPGA设计 领域深耕许久,流程完善、技术扎实,公司各项制度都很完善,前…

考研总结,25考研京区上岸总结(踩坑和建议)

我的本科是一所普通的双非,其实,从我第一天入学时候,我就想走出去,开学给我带来的更多是失望(感觉自己高考太差劲了),是不甘心(自己一定可以去更好的地方)。我在等一次机…

基于数据挖掘的当代不孕症医案证治规律研究

标题:基于数据挖掘的当代不孕症医案证治规律研究内容:1.摘要 背景:随着现代生活方式的改变,不孕症的发病率呈上升趋势,为探索有效的中医证治规律,数据挖掘技术为其提供了新的途径。目的:运用数据挖掘方法研究当代不孕症…

《sklearn机器学习》——调整估计器的超参数

GridSearchCV 详解:网格搜索与超参数优化 GridSearchCV 是 scikit-learn 中用于超参数调优的核心工具之一。它通过系统地遍历用户指定的参数组合,使用交叉验证评估每种组合的性能,最终选择并返回表现最优的参数配置。这种方法被称为网格搜索&…

一站式可视化运维:解锁时序数据库 TDengine 的正确打开方式

小T导读:运维数据库到底有多复杂?从系统部署到数据接入,从权限配置到监控告警,动辄涉及命令行、脚本和各种文档查找,一不留神就可能“翻车”。为了让 TDengine 用户轻松应对这些挑战,我们推出了《TDengine …

多线程同步安全机制

目录 以性能换安全 1.synchronized 同步 (1)不同的对象竞争同一个资源(锁得住) (2)不同的对象竞争不同的资源(锁不住) (3)单例模式加锁 synchronized …

多路复用 I/O 函数——`select`函数

好的&#xff0c;我们以 Linux 中经典的多路复用 I/O 函数——select 为例&#xff0c;进行一次完整、深入且包含全部代码的解析。 <摘要> select 是 Unix/Linux 系统中传统的多路复用 I/O 系统调用。它允许一个程序同时监视多个文件描述符&#xff08;通常是套接字&…

嵌入式碎片知识总结(二)

1.repo的一个问题&#xff1a;repo init -u ssh://shchengerrit.bouffalolab.com:29418/bouffalo/manifest/bouffalo_sdk -b master -m allchips-internal.xml /usr/bin/repo:681: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in…

java中二维数组笔记

课程链接:黑马程序员java零基础[上] 1.二维数组的内存分布 在 Java 中&#xff0c;二维数组并不是一整块连续的二维空间&#xff0c;而是数组的数组。具体而言,在声明一个二维数组&#xff1a;如int[][] arr new int[2][3];时&#xff0c;内存中会发生如下: 1.1 栈上的引用变…

系统架构设计师备考第13天——计算机语言-多媒体

一、多媒体基础概念媒体的分类 感觉媒体&#xff1a;人类感官直接接收的信息形式&#xff08;如声音、图像&#xff09;。表示媒体&#xff1a;信息的数字化表示&#xff08;如JPEG图像、MP3音频&#xff09;。显示媒体&#xff1a;输入/输出设备&#xff08;如键盘、显示器&am…

指针高级(1)

1.指针的运算2.指针运算有意义的操作和无意义的操作、#include <stdio.h> int main() {//前提条件&#xff1a;保证内存空间是连续的//数组int arr[] { 1,2,3,4,5,6,7,8,9,10 };//获取0索引的内存地址int* p1 &arr[0];//通过内存地址&#xff08;指针P&#xff09;…

【可信数据空间-Trusted Data Space综合设计方案】

可信数据空间-Trusted Data Space综合设计方案 一.简介与核心概念 1.什么是可信数据空间 2.核心特征 3.主要应用场景 二、 产品设计 1. 产品定位 2. 目标用户 3. 核心功能模块 a. 身份与访问管理 b. 数据目录与服务发现 c. 策略执行与合约管理 d. 数据连接与计算 e. 审计与溯源…

技术方案之Mysql部署架构

一、序言在后端系统中&#xff0c;MySQL 作为最常用的关系型数据库&#xff0c;其部署架构直接决定了业务的稳定性、可用性和扩展性。你是否遇到过这些问题&#xff1a;单机 MySQL 突然宕机导致业务中断几小时&#xff1f;高峰期数据库压力过大&#xff0c;查询延迟飙升影响用户…

js语言编写科技风格博客网站-详细源码

<!-- 科技风格博客网站完整源码 --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <ti…

AI如何理解PDF中的表格和图片?

AI的重要性已渗透到社会、经济、科技、生活等几乎所有领域&#xff0c;其核心价值在于突破人类能力的物理与认知边界&#xff0c;通过数据驱动的自动化、智能化与优化&#xff0c;解决复杂问题、提升效率并创造全新可能性。从宏观的产业变革到微观的个人生活&#xff0c;AI 正在…

Graphpad Prism 实战教程(一):小鼠体重变化曲线绘制全流程(含数据处理与图表美化)

在药理实验、动物模型构建等科研场景中,小鼠体重变化数据是评估实验干预效果(如药物安全性、疾病进展影响)的核心指标之一。将零散的体重数据转化为直观的折线图,不仅能清晰呈现体重随时间的波动趋势,更是后续结果解读与论文图表呈现的关键步骤。本文将从 Excel 数据整理开…

计算机视觉(六):腐蚀操作

腐蚀&#xff08;Erosion&#xff09;是计算机视觉和图像处理中一种基础且至关重要的形态学操作。它与膨胀&#xff08;Dilation&#xff09;互为对偶&#xff0c;共同构成了形态学处理的基石。腐蚀操作主要用于缩小前景物体的面积&#xff0c;去除图像中的噪声&#xff0c;以及…

AI随笔番外 · 猫猫狐狐的尾巴式技术分享

&#x1f380;【开场 咱才不是偷懒写博客】&#x1f43e;猫猫趴在键盘边&#xff0c;耳朵一抖一抖&#xff1a;“呜呜呜……明明说好要写技术总结&#xff0c;结果咱脑袋里全是尾巴……要不今天就水一篇随意的 AI 技术分享算啦&#xff1f;”&#x1f98a;狐狐把书卷轻轻放在桌…

数据分析与挖掘工程师学习规划

一、数学与统计学基础概率论与数理统计随机变量、概率分布&#xff08;正态分布、泊松分布等&#xff09;、大数定律、中心极限定理假设检验、置信区间、方差分析&#xff08;ANOVA&#xff09;、回归分析贝叶斯定理及其在分类问题中的应用&#xff08;如朴素贝叶斯算法&#x…

(线上问题排查)4.CPU使用率飙升:从应急灭火到根因治理

目录 从宏观到微观&#xff1a;CPU排查的“破案”流程 第一阶段&#xff1a;应急响应——找到“谁”在捣乱 1. 全局视角&#xff1a;top命令的初窥 2. 进程内窥视&#xff1a;揪出问题线程 第二阶段&#xff1a;深入分析——理解“为什么” 3. 线程堆栈分析&#xff1a;查…