接口幂等性是什么?如何设计?

**摘要:**本文从接口幂等性的定义出发,说明为什么在分布式系统和微服务架构中必须关注幂等,并系统梳理唯一索引、Token 机制、状态机、乐观锁、分布式锁、去重表等常见设计方案,最后给出选型建议与落地注意事项。

一、什么是接口幂等性

接口幂等性(Idempotency)指的是:同一个请求被执行一次和执行多次,对系统产生的业务影响是一致的。换句话说,调用方因为超时、网络抖动、重试等原因重复提交了同一个请求,服务端最终的业务结果应当与只提交一次相同。

需要特别区分两个概念:

  • 幂等 ≠ 结果完全相同:多次调用返回的响应体可以不同(例如第二次返回"已处理"),但业务状态不能因为重复调用而再次变化。
  • 幂等 ≠ 并发安全:幂等解决的是"重复请求"问题,并发安全解决的是"同时请求"问题,两者常常需要配合使用。

二、为什么需要幂等性

在单体应用中,一次请求通常只经过一次数据库操作,重复提交的概率较低。但在分布式系统中,重复请求几乎不可避免:

  • 网络超时重试:客户端未收到响应,自动重试,服务端可能已经处理成功。
  • 网关与 RPC 框架重试:Feign、Dubbo、Nginx 等组件在超时后可能发起重试。
  • 消息队列重复投递:Kafka、RocketMQ 在消费失败或 rebalance 时会重复投递消息。
  • 用户误操作:用户连续点击"提交订单""支付"按钮。
  • 定时任务重复调度:多实例部署时任务被多个节点同时触发。

如果接口不具备幂等性,重复请求可能导致重复扣款、重复下单、库存超卖、重复发券等严重业务问题。

三、哪些接口需要幂等设计

并不是所有接口都需要幂等。通常按 HTTP 语义和业务性质区分:

请求类型 是否天然幂等 说明
GET 是 只读查询,不应修改数据
PUT 是 全量更新,多次执行结果一致
DELETE 是 删除同一资源,多次执行结果一致
POST 否 通常用于创建资源,需要额外设计幂等

实际业务中,支付、下单、退款、发券、扣库存、消息消费等写操作是幂等设计的重点场景。

四、常见幂等设计方案

4.1 唯一索引 + 数据库约束

最直接、最可靠的方式。为业务唯一键建立唯一索引,重复插入时数据库会抛出唯一约束异常,服务端捕获后返回"已处理"。

sql 复制代码
CREATE TABLE t_order (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(64) NOT NULL,
    user_id BIGINT NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_order_no (order_no)
);

优点是实现简单、由数据库保证强一致性;缺点是依赖唯一键设计,且异常处理需要区分"唯一冲突"和"其他错误"。

4.2 Token 机制(防重令牌)

流程如下:

  1. 客户端在进入提交页面前,先调用服务端接口获取一个全局唯一的 Token。
  2. 服务端将 Token 存入 Redis,设置合理过期时间。
  3. 客户端提交请求时携带该 Token。
  4. 服务端使用 DEL 或 Lua 脚本原子性地删除 Token,删除成功才继续执行业务,删除失败说明是重复请求。
java 复制代码
public boolean tryAcquireToken(String token) {
    // 使用 DEL 的返回值判断是否首次消费,保证原子性
    Long deleted = redisTemplate.opsForValue().getOperations()
            .delete(TOKEN_PREFIX + token) ? 1L : 0L;
    return deleted != null && deleted > 0;
}

该方案适合前端表单提交、下单等场景,能有效拦截用户重复点击。注意 Token 必须一次性使用,且过期时间不宜过长。

4.3 状态机 + 条件更新

对于有明确状态流转的业务(如订单:待支付 → 已支付 → 已发货),可以通过状态判断实现幂等:只有处于前置状态的记录才允许流转。

sql 复制代码
UPDATE t_order
SET status = 1, pay_time = NOW()
WHERE order_no = #{orderNo}
  AND status = 0;

根据受影响行数判断是否执行成功:返回 1 表示本次真正完成了状态流转,返回 0 表示已被处理过或状态不合法。这种方式天然幂等,且能防止并发下的重复流转。

4.4 乐观锁(版本号)

在表中增加 version 字段,更新时带上版本号条件,版本不匹配则更新失败。

sql 复制代码
UPDATE t_account
SET balance = balance - #{amount}, version = version + 1
WHERE user_id = #{userId}
  AND version = #{version}
  AND balance >= #{amount};

乐观锁适合读多写少、冲突较少的场景。若冲突频繁,重试成本会上升,此时可考虑悲观锁或分布式锁。

4.5 分布式锁

通过 Redis、ZooKeeper 等实现分布式互斥锁,保证同一业务键在同一时刻只有一个请求在执行。

java 复制代码
String lockKey = "lock:order:" + orderNo;
boolean locked = redisTemplate.opsForValue()
        .setIfAbsent(lockKey, "1", Duration.ofSeconds(30));
if (!locked) {
    throw new BizException("请求处理中,请勿重复提交");
}
try {
    // 执行业务逻辑
} finally {
    redisTemplate.delete(lockKey);
}

注意:分布式锁只能保证"同一时刻不并发",不能保证跨时间的重复请求幂等。因此它通常与唯一索引、状态机等方案组合使用。

4.6 去重表 / 幂等表

建立一张独立的幂等记录表,以业务唯一标识(如请求 ID、业务流水号)作为唯一索引。每次请求先插入幂等记录,插入成功才执行业务,插入冲突则直接返回上次结果。

sql 复制代码
CREATE TABLE t_idempotent_record (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    biz_type VARCHAR(32) NOT NULL,
    biz_key VARCHAR(128) NOT NULL,
    result TEXT,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_biz (biz_type, biz_key)
);

该方案通用性强,适合跨服务、跨表的复杂业务,但会引入额外的存储和一次写操作,需要评估性能开销。

五、方案对比与选型建议

方案 实现复杂度 适用场景 主要局限
唯一索引 低 创建类操作,有天然唯一键 依赖唯一键设计
Token 机制 中 前端表单、下单提交 需要额外获取 Token
状态机 低 有明确状态流转的业务 仅适用于状态型业务
乐观锁 低 更新类操作,冲突较少 高冲突时重试成本高
分布式锁 中 并发互斥控制 不解决跨时间重复
去重表 中高 跨服务复杂业务 额外存储与写开销

选型时建议遵循以下原则:

  • 优先用数据库约束兜底:唯一索引是最可靠的最后一道防线。
  • 按业务语义选择:状态型业务用状态机,创建型业务用唯一索引或 Token。
  • 组合使用:前端 Token 拦截重复点击,后端唯一索引或状态机保证最终一致。
  • 控制粒度:幂等键要能唯一标识一次业务操作,避免粒度过粗导致正常请求被误拦。

六、落地注意事项

  • 幂等键的设计:优先使用业务流水号、订单号等全局唯一标识;若由客户端生成,需防止伪造和碰撞。
  • 幂等记录的过期与清理:Redis Token 需设置 TTL,去重表需定期归档,避免无限增长。
  • 返回结果的一致性:重复请求应返回与首次一致或明确的"已处理"结果,避免调用方误判为失败而继续重试。
  • 事务边界:幂等判断与业务操作应尽量在同一事务或同一原子操作内完成,防止判断通过后业务失败导致状态不一致。
  • 可观测性:对拦截到的重复请求打点监控,便于发现上游重试策略异常或客户端 bug。

七、总结

接口幂等性是分布式系统稳定性的基础保障。它的核心不是"防止请求重复到达",而是"保证重复到达不产生副作用"。实际落地时,应结合业务语义选择合适的方案,并以数据库唯一约束作为最终兜底,同时配合 Token、状态机、分布式锁等手段形成多层防护。

相关推荐
ACP广源盛139246256731 小时前
6 盘位存储扩展方案选型笔记:YLB3116 @ACP评估记录
笔记·硬件架构·硬件工程·国产芯片
小李不想当小白1 小时前
SPI通信外设与W25Q64 (硬件SPI)(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
weixin_448119944 小时前
Datawhale Jev Cookbook 202610 笔记3
笔记
姚瑞南4 小时前
【PRD】招投标商机雷达爬虫系统
人工智能·经验分享·笔记·爬虫·产品运营·产品经理
Gnevergiveup4 小时前
ATT&CK红蓝对抗实战指南-Windows安全基础
笔记·网络安全
江屿风7 小时前
【Plain Language Large Model】【从麦当劳点餐到Redis接入:MCP如何让AI工具集成变简单】流食般投喂
人工智能·redis·笔记·语言模型·llm·mcp
海绵宝宝转agent8 小时前
Leetcode二叉树算法个人理解
笔记·学习·算法·leetcode·深度优先
小oo呆8 小时前
【学习心得】MCP工具开发学习笔记
笔记·学习
2601_961146209 小时前
实验室用等离子表面处理设备处理汽车PP内饰件和PBT骨架的一点记录
笔记·汽车·设备