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

相关推荐
笨鸟先飞的橘猫18 小时前
skynet——分布式支持
分布式·skynet
ACP广源盛139246256731 天前
GSV6155 @ACP#工业车规 DP1.4 重定时器 Retimer
大数据·人工智能·分布式·嵌入式硬件
Geek-Chow1 天前
微服务认证与授权:06 — Keycloak / IdP
微服务·信息可视化·架构
ACP广源盛139246256731 天前
GSV2221@ACP# DP1.4 MST 多流显示转换芯片国产工业多屏
大数据·人工智能·分布式·嵌入式硬件·spark
我叫张小白。1 天前
一个微服务电商+社区项目(瓷韵app)的技术深度复盘
docker·微服务·云原生·架构
ACP广源盛139246256731 天前
GSV2231@ACP# 增强型 8K MST 多屏扩展芯片
大数据·人工智能·分布式·嵌入式硬件
虚心的百褶裙1 天前
远程调用服务架构设计及zookeeper技术详解(下篇)
分布式·zookeeper·云原生
Geek-Chow1 天前
微服务认证与授权:08 — OPA(PDP)
微服务·云原生·架构
阿标在干嘛1 天前
100+微服务的统一出入口:政策快报平台的API网关设计
微服务·云原生·架构
清晨曦月1 天前
【从零构建分布式在线评测系统】二、Judge0服务器上的心跳程序
运维·服务器·分布式