当前位置: 首页 > news >正文

高并发下如何保证接口的幂等性


文章目录

  • 引言
  • Token 机制(前端防重)
  • 数据库唯一索引去重(兜底)
  • 分布式锁
  • 状态机幂等(业务逻辑层面)
  • 乐观锁(适用于更新操作)
  • 方案比对
  • 总结

引言

如何在高并发的情况下,保证各个接口的幂等性,是C端业务的必做逻辑,同时这也是面试中重要的场景题。那么下面介绍一下什么是幂等性

在高并发场景下,幂等性(Idempotency)是确保系统稳定性、防止数据因重试或并发请求而产生脏数据的核心机制。简单来说,幂等性要求对于同一个请求,执行一次和执行多次的效果必须是一致的。

作为后端开发人员(Java/Go),处理这个问题通常需要从“业务设计”和“技术手段”两个维度切入

Token 机制(前端防重)

这是应对“表单重复提交”最常用的方案。其核心思想是将“请求意图”与“执行动作”分离。

  • 流程:

    1. 获取 Token:客户端在执行操作前,先从服务端申请一个全局唯一的 Token(通常存入 Redis,并设置过期时间)。

    2. 携带 Token 提交:客户端在业务请求的 Header 或 Body 中携带该 Token。

    3. 校验与删除:服务端接收请求后,利用 Redis 的原子性操作(如DEL或 Lua 脚本)判断 Token 是否存在。

      • 如果删除成功:说明是第一次请求,执行业务逻辑。
      • 如果删除失败:说明是重复请求,直接返回。

这种防重复机制适用于表单提交、支付等前端可控场景,下面是示例代码

// 申请 tokenStringtoken=UUID.randomUUID().toString();redis.set("idempotent:"+token,"1",5,TimeUnit.MINUTES);// 执行接口时校验(原子操作,防并发)Booleansuccess=redis.delete("idempotent:"+token);// 只有一个并发能删成功if(!success){thrownewRepeatSubmitException("请求重复或token无效");}// 执行业务逻辑...

数据库唯一索引去重(兜底)

利用数据库层面的Unique Key约束,这是防止产生重复数据最简单且最可靠的手段。

  • 场景:比如订单号、支付流水号。
  • 实现:在数据库中建立唯一索引。当并发请求到达时,只有第一个插入请求会成功,后续请求会触发Duplicate Key异常。
  • 技巧:在 Java 中可以捕获DuplicateKeyException;在 Go 中判断错误码
// 订单表建唯一索引// UNIQUE KEY uk_order (biz_id, request_id)try{orderMapper.insert(order);// 包含 request_id}catch(DuplicateKeyExceptione){// 幂等:查询已有结果返回returnorderMapper.selectByRequestId(requestId);}

分布式锁

我们可以使用分布式锁来实现去重,分布式锁的实现方式有很多,比如redis、zookeeper、Etcd等,下面我们以redis来演示。

通过 Redis 的SETNX或 Redlock 来实现。

  • 逻辑

    1. 进入业务逻辑前,先尝试以业务唯一标识(如order_id)作为 Key 加锁。
    2. 如果加锁成功,执行业务逻辑。
    3. 如果加锁失败,说明已有相同请求在处理中,直接返回。
  • 注意:锁的释放时间需要根据业务耗时合理评估,防止业务没跑完锁就过期了

StringlockKey="idempotent:"+bizId+":"+requestId;// SETNX + 过期时间,原子操作Booleanlocked=redis.setIfAbsent(lockKey,"PROCESSING",10,TimeUnit.SECONDS);if(!locked){// 并发请求:等待或查询已有结果returnqueryResult(requestId);}try{// 1. 再次查询是否已处理(double check)Resultexisting=queryResult(requestId);if(existing!=null)returnexisting;// 2. 执行业务Resultresult=doBusiness();// 3. 持久化结果saveResult(requestId,result);returnresult;}finally{redis.delete(lockKey);}

状态机幂等(业务逻辑层面)

对于有状态流转的业务(如订单:待支付 -> 已支付 -> 已出库),可以通过状态约束实现幂等。

在下面的SQL中,我们就使用了UPDATE ... WHERE status = 'UNPAID'的行级锁天然保证幂等,这是最推荐的方式之一,无额外中间件依赖,利用数据库行锁天然防并发。这条 SQL 无论执行多少次,只有第一次能更新成功(影响行数为 1),后续执行由于状态已改变,影响行数均为 0

UPDATEordersSETstatus='PAID'WHEREid=123ANDstatus='UNPAID';

那么相关的业务代码可以这样来写,通过数据库的反馈来决定代码走向

intaffected=orderMapper.casUpdateStatus(orderId,"UNPAID","PAID");if(affected==0){// 说明已被处理,直接返回当前状态returnorderMapper.selectById(orderId);}

乐观锁(适用于更新操作)

通过版本号(version)来控制。

  • 实现:在表里加一个version字段。
  • 逻辑:每次更新时,要求版本号必须匹配
UPDATEaccountSETbalance=balance-100,version=version+1WHEREid=1ANDversion=5;

在并发冲突时,只有一个请求能命中版本号

方案比对

我们可以根据自己的业务场景和性能容忍度来决定使用什么方案

方案适用场景优点缺点
Token 机制前端提交、防重点击逻辑通用,不依赖数据库类型需要多一次网络交互获取 Token
唯一索引插入新数据(新增用户/订单)实现最简单,可靠性最高无法处理逻辑复杂的更新操作
分布式锁极高并发下的写操作性能高,能有效拦截重复请求增加了对 Redis 的强依赖
状态机业务流程流转无额外性能损耗,逻辑优雅仅适用于有状态变化的场景
乐观锁账户余额、库存扣减简单易行,无锁竞争高竞争下重试率高,性能下降

下面还有一些要注意避坑的点:

  • 先查后改(Check-then-Act)的陷阱:在高并发下,if (not exists) { insert }这种逻辑在没有锁的情况下是失效的,一定要利用数据库原子性或分布式锁。

  • Token 的删除时机:建议先删除 Token 再执行业务(或者使用 Lua 脚本保证原子性)。如果执行完业务再删 Token,在高并发瞬间可能有两个请求都通过了“查 Token”的校验。

  • 返回一致性:当识别出是重复请求时,通常应该返回“处理中”或“执行成功(返回上次的结果)”,而不是直接报错,以提升用户体验。

总结

经过上面各种方案的讨论,保证接口的幂等性是我们后端必须要注意的地方,如果业务和代码没有保障这一块,那么很可能造成严重后果和资损,那么我们只能卷铺盖跑路了(Doge)

http://www.jsqmd.com/news/425316/

相关文章:

  • CF958F2 Lightsabers (medium)题解
  • 【AI渗透】——专为渗透测试工程师和安全研究员设计的新一代集成化安全测试平台(Venom)
  • 一款基于 .NET 开源免费、高效且用户友好文件搜索工具!
  • 基于粒子群算法的含分布式电源的主动配电网电压—有功-无功优化研究:以IEEE33节点为例
  • .Android Compose 基础系列:您的第一个 Kotlin 程序
  • 借助MongoDB实现大数据的分布式存储
  • MiniRAG + LLM (二)
  • 一文梳理清大数据领域CAP定理,轻松驾驭数据
  • 电动汽车充放电调度优化:全局与局部方案的比较及性能分析
  • 鸿蒙应用开发UI基础第十四节:文本显示组件Text核心讲解与实战演示 - 鸿蒙
  • Java求职面试实战:微服务与安全框架场景问题解析
  • 玩转STM32F1驱动双雄:BLDC与PMSM的攻防战
  • 从 Java 到 Go:一场性能革命
  • 使用C语言实现STM的启动文件
  • 探索大数据领域Doris的核心特性与优势
  • AI推理能力革命:如何打造高性能原生应用?
  • Android 开发问题:FileProvider: java.lang.SecurityException: Provider must not be exported
  • 大数据时代:用户画像助力企业精准营销
  • 使用 pkgutil 实现动态插件系统
  • 自注意力机制详解:从原理到计算过程
  • 东莞直饮水机服务商怎么选?靠谱服务商推荐 - 小坤哥
  • 记一次AI Agent开发的思维误区
  • 其他-vscode-配置
  • 最小二乘问题详解:线性最小二乘实例
  • ZooKeeper 的 Watcher 机制的底层实现
  • macos:从命令行启动device模拟器
  • 在手机上运行AI模型
  • 创新是改良式的(Incremental Innovation),但是,有些创新是颠覆式的(Disruptive Innovation ...
  • OpenClaw 安装与配置API教程(Mac电脑,超详细喂饭)
  • 【节点】[DielectricSpecular节点]原理解析与实际应用