分布式接口幂等性设计:唯一索引、Token 与分布式锁

接口幂等性解决的是"同一个请求被执行多次,会不会造成重复业务效果"的问题。用户重复点击、网络重试、MQ 重复消费,都可能让同一业务被重复执行。

一句话概括:幂等就是多次调用和一次调用的业务结果一致;查询和按唯一值删除天然幂等,新增、支付、转账、发券这类写操作必须额外设计幂等。

GET 查询
DELETE 按唯一值删除
POST 新增或支付
PUT 增量更新
请求进入业务接口
是否天然幂等
直接处理
需要幂等设计
数据库唯一索引
Token + Redis
分布式锁

什么是幂等

幂等的定义是:多次调用方法或接口,不会改变业务状态,重复调用结果和单次调用结果一致。

常见需要幂等的场景:

  • 用户重复点击提交按钮。
  • 网络波动导致前端或网关重试。
  • MQ 消息重复投递。
  • 应用超时后触发重试机制。
  • 支付、转账、下单、发券等写操作。

从 RESTful 看幂等性

请求方式 是否幂等 说明
GET 幂等 查询操作不改变状态
POST 通常不幂等 新增请求执行多次会插入多条数据
PUT 看写法 绝对值更新幂等,增量更新不幂等
DELETE 通常幂等 按唯一 ID 删除,多次删除结果一致

比如:

sql 复制代码
update t_item set money = 500 where id = 1;

这是幂等的,因为执行多次结果都是 500。

sql 复制代码
update t_item set money = money + 500 where id = 1;

这不是幂等的,因为执行多次会一直累加。

数据库唯一索引

新增类业务可以通过唯一索引防重复。

比如订单表有业务订单号 order_no,给它加唯一索引:

sql 复制代码
create unique index uk_order_no on t_order(order_no);

同一个订单号重复插入时,数据库会拒绝第二次插入。

适合场景:

  • 创建订单。
  • 创建支付流水。
  • MQ 消费记录。
  • 发券记录。

唯一索引是最硬的兜底,优先级很高。

Token + Redis

Token 方案适合提交订单、转账、支付这类"前端先申请令牌,再带令牌提交"的场景。
存在
不存在
客户端请求 token
服务端生成唯一 token
token 存入 Redis
返回 token 给客户端
客户端携带 token

请求业务接口
Redis 中 token 是否存在
删除 token
执行业务
重复请求

直接返回

关键点是:验证 token 和删除 token 必须是原子操作。否则两个重复请求同时进来,都可能判断 token 存在。

实际项目里可以用 Redis Lua 脚本完成"判断 + 删除"。

示例脚本:

lua 复制代码
local tokenKey = KEYS[1]
local exists = redis.call('exists', tokenKey)
if exists == 1 then
    redis.call('del', tokenKey)
    return 1
else
    return 0
end

Java 侧伪代码可以这样理解:

java 复制代码
Long result = redisTemplate.execute(script, List.of("submit:token:" + token));
if (result == 1) {
    // token 存在且已删除,可以执行业务
    createOrder();
} else {
    // token 不存在,说明是重复提交或非法请求
    return;
}

为什么要用 Lua?因为 Redis 执行 Lua 脚本是原子的,existsdel 不会被其他请求插队。这样两个重复请求同时到达时,只有一个请求能删除成功并继续执行业务。

分布式锁

分布式锁也能控制重复执行。

典型写法是用业务唯一值作为锁 key:

java 复制代码
RLock lock = redissonClient.getLock("order:" + orderNo);
boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
try {
    if (!locked) {
        throw new RuntimeException("重复提交");
    }
    // 执行业务
} finally {
    if (locked) {
        lock.unlock();
    }
}

使用分布式锁要注意:

  • 锁粒度要小,最好按业务单号加锁。
  • 抢不到锁要快速失败。
  • 要保证 finally 释放锁。
  • 高并发场景下性能比 Token 或唯一索引更重。

方案怎么选

方案 适合场景 优点 注意点
唯一索引 新增、流水、消费记录 简单可靠,数据库兜底 需要设计业务唯一键
Token + Redis 表单提交、支付、转账 性能好,体验清晰 校验和删除要原子
分布式锁 新增或修改都要串行 控制范围灵活 性能较低,锁粒度要小

面试回答模板

可以这样答:

幂等是指多次调用接口不会改变业务状态,重复调用和单次调用的结果一致。查询接口天然幂等,POST 新增、支付、转账、MQ 消费这类场景需要额外设计。新增数据可以用数据库唯一索引,比如订单号或消息 ID 做唯一约束。提交订单、支付等场景可以用 Token + Redis,第一次请求生成 token 存入 Redis,第二次提交时携带 token,服务端验证 token 存在后删除并执行业务,不存在就认为是重复请求。新增或修改也可以用分布式锁,但性能相对低,要控制锁粒度。

小结

幂等设计的核心不是"防重复提交"一句话,而是识别业务唯一性。

能用唯一索引兜底就先兜底;需要前置防重复就用 Token;需要串行化处理再考虑分布式锁。

相关推荐
剑胆琴心静水深流12 分钟前
全栈之路6---web集成与呈现
前端·vue.js·spring boot·分布式·spring·前端框架·npm
KAIWEILIUCC4 小时前
Ray:高性能、易扩展的Python分布式计算框架
分布式·llm
一路向北finish8 小时前
《CentOS 7 搭建 Hadoop 3.3.6 分布式集群:NFS 共享方案完整实操》
linux·运维·hadoop·分布式·centos
天涯明月19939 小时前
Ray 架构解析——以动态任务图统一 AI 计算的分布式框架
大数据·人工智能·分布式·架构·ray
星期一研究室10 小时前
为什么有些文档你永远不想打开第二次
微服务·产品·设计
小张同学a.10 小时前
MooseFS 分布式存储实战指南:部署、运维与 Pacemaker+SBD 高可用落地
linux·运维·分布式·pacemaker·moosefs·sbd
BYSJMG11 小时前
大数据毕业设计选题方向|【基于大数据的燃油消耗数据可视化与分析】Hadoop+PySpark+FP-Growth
大数据·hadoop·分布式·信息可视化·数据分析·课程设计
NJCloud1 天前
MooseFS 分布式存储部署与高可用架构实践
linux·运维·分布式·架构
zcmodeltech1 天前
钢铁冶金沙盘模型控制系统设计与实现:高炉-连铸-热轧多工序联动方案
分布式·stm32·单片机·嵌入式硬件·交互
lisin-lee-cooper1 天前
简单聊聊 OpenFeign 框架源码
java·微服务