分布式接口幂等性设计:唯一索引、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;需要串行化处理再考虑分布式锁。

相关推荐
还在忙碌的吴小二14 小时前
TLog 分布式日志追踪新手入门指南
分布式
轻刀快马14 小时前
从底层 CPU 架构看透现代分布式与并发编程
分布式·架构·cpu
晚烛14 小时前
CANN 分布式通信与 HCCL:多 NPU 协作的底层机制
开发语言·人工智能·分布式·python·深度学习
青云计划14 小时前
分布式单飞锁
分布式
会编程的土豆14 小时前
Kafka 零基础入门(最基本用法)
分布式·kafka
会编程的土豆14 小时前
Kafka 入门笔记(核心语法 + 用法)
笔记·分布式·kafka
song50115 小时前
多模态模型在昇腾上的部署架构
人工智能·分布式·深度学习·架构·transformer·交互
过期动态15 小时前
【RabbitMQ高级篇】生产者可靠性、MQ可靠性、消费者可靠性以及延迟队列的实现
java·数据结构·分布式·算法·rabbitmq·ruby
万里侯1 天前
Kubernetes Operator模式:自动化运维的高级实践
微服务·容器·k8s