Spring Boot 3.x 优雅实现接口幂等:4 种方案一次讲透
引言
上个月运营做了一场大促预售,活动开始 10 分钟后客服反馈开始堆积:同一个用户下了两单。查日志发现不是并发 bug,也不是库存逻辑问题——用户在网络卡顿时连点了两次提交按钮,第一次请求 300ms 才返回,第二次请求几乎是同时到达的。前端虽然有按钮置灰,但置灰逻辑在“请求发出后”才生效,两道请求都穿了过去。
这类问题防得住吗?防得住,而且手段成熟——接口幂等。但幂等实现恰恰是“看起来简单、做对很难”的典型:网上随手搜到的 Redis SETNX 三行代码,放进真实系统里至少有四个坑等着你(并发窗口、异常回滚、过期时间、重复提交的用户体验)。
这篇文章用那次事故开题,把四种幂等方案一次讲透:唯一索引、Redis SETNX、Token 机制、乐观锁。每种给完整可跑的 Spring Boot 3.x 代码、适用场景、防坑要点——最后给一张选型决策表,照着抄就行。
一、为什么需要幂等:不只是双击
1.1 重复请求的四个来源
双击只是冰山一角。生产系统里重复请求的来源比想象中多:
| 来源 | 场景 | 特点 |
|---|---|---|
| 用户行为 | 双击提交、刷新重发、返回键重放 | 单用户、低频、难彻底在前端防住 |
| 网络重试 | HTTP 客户端超时重试、网关重试、TCP 重传 | 无法禁用(网络重试是容错必备) |
| 消息重复 | MQ at-least-once 投递、消费者重试 | 分布式系统的常态,不是异常 |
| 定时任务/对账 | 任务重跑、补偿逻辑重复触发 | 时间间隔长,容易被遗忘 |
关键认知:重复请求不是要“消灭”的 bug,而是要“容忍”的常态。下游服务必须做到“同一个业务请求来 100 次,效果等于 1 次”——这就是幂等。HTTP 规范其实早已约定 GET/PUT/DELETE 天然幂等,真正需要工程手段的是 POST 这类非幂等操作(下单、支付、提交)。
1.2 幂等的本质:业务唯一键 + 执行前检查
剥掉所有方案的外壳,幂等的骨架就一句话:给业务请求找一个“天然或人工的唯一键”,在执行真正的业务逻辑前检查这个键是否已经处理过。
四种方案的本质区别只有一个:这个“唯一键 + 检查”放在哪一层、用什么技术承载。
| 方案 | 唯一键放哪 | 检查载体 | 强一致 |
|---|---|---|---|
| 唯一索引 | 数据库表字段 | DB 唯一约束 | ✅ 最高 |
| Redis SETNX | Redis key(防重表) | SETNX 原子操作 | 中(Redis 可用性内) |
| Token 机制 | 服务端预发 token | Redis 删除原子性 | 中 |
| 乐观锁 | 数据行版本号 | UPDATE 条件语句 | ✅ 最高 |
二、方案一:唯一索引——最可靠的兜底
2.1 实现:靠数据库约束兜底
-- 订单表:userId + productId + 活动Id 组成业务唯一键
ALTER TABLE t_order
ADD UNIQUE KEY uk_user_product_activity (user_id, product_id, activity_id);
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
public Order createOrder(OrderReq req) {
Order order = buildOrder(req);
try {
orderMapper.insert(order); // 依赖 DB 唯一索引
} catch (DuplicateKeyException e) { // Spring 翻译好的重复键异常
throw new BizException("ORDER_DUPLICATE", "请勿重复下单");
}
return order;
}
}
2.2 适用场景与定位
唯一索引是所有方案的“最后防线”,而不是首选方案:
- ✅ 适合:数据天然有唯一键的场景(用户+商品+活动的限购单、流水号驱动的支付单);
- ❌ 不适合:业务本身没有天然唯一键的请求(“创建一条备注”没法造唯一键);
- ⚠️ 它不防止“业务动作执行了两遍”,只防止“落了两条数据”——如果下单前置逻辑(扣库存、发 MQ)在 insert 之外,唯一索引拦住重复 insert 时,前置动作可能已经执行过(见 2.3 坑 2)。
2.3 防坑要点
| 坑 | 说明 | 对策 |
|---|---|---|
| 坑 1:唯一键设计太宽或太窄 | 太宽(只按 userId)→ 正常需求被误杀;太窄(含时间戳)→ 永远拦不住 | 业务唯一键要经过产品确认,如"同一活动同一用户同一商品” |
| 坑 2:异常只拦住了最后一环 | insert 前的扣库存/发消息已执行,抛 DuplicateKey 后这些动作不会自动回滚 | 唯一索引方案必须包在同一个 DB 事务里;跨资源动作放 insert 成功之后 |
| 坑 3:批量插入一颗老鼠屎 | batchInsert 里一条重复,整批失败 | 拆单条 + IGNORE/ON DUPLICATE KEY,或逐条处理冲突 |
| 坑 4:分库分表后唯一索引失效 | 唯一约束只在单库单表内有效 | 分片键必须包含唯一键字段,或改用全局防重层(方案二) |
三、方案二:Redis SETNX + 过期时间——推荐主力(完整代码)
3.1 设计:防重令牌 = 业务前缀 + 唯一标识 + 有效期
/**
* 通用幂等切面:注解 + AOP + Redis SETNX
* Spring Boot 3.x / Spring Boot 2.x 通用(依赖 spring-boot-starter-data-redis)
*/
/** ① 注解定义 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
/** Redis key 中的 SpEL 表达式,从入参提取业务唯一标识 */
String key();
/** 防重有效期(须 ≥ 业务最大处理时长 + 网络往返余量) */
int expireSeconds() default 10;
/** 重复请求的提示语 */
String message() default "请求处理中,请勿重复提交";
}
/** ② 切面实现 */
@Aspect
@Component
@RequiredArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE) // 幂等检查要在事务开启前执行
public class IdempotentAspect {
private final StringRedisTemplate redis;
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint pjp, Idempotent idempotent) throws Throwable {
String redisKey = buildKey(pjp, idempotent);
// 核心:SETNX + 过期时间,一条 Lua 保证原子性
Boolean acquired = redis.opsForValue().setIfAbsent(
redisKey, "1", idempotent.expireSeconds(), TimeUnit.SECONDS);
if (Boolean.FALSE.equals(acquired)) {
throw new BizException("IDEMPOTENT_REJECT", idempotent.message());
}
try {
return pjp.proceed();
} catch (Throwable e) {
// 关键决策:业务异常时删除防重键,允许用户重试(见 3.3 坑 2)
redis.delete(redisKey);
throw e;
}
}
private String buildKey(ProceedingJoinPoint pjp, Idempotent idempotent) {
// SpEL 解析:@Idempotent(key = "#req.userId + ':' + #req.activityId")
ExpressionParser parser = new SpelExpressionParser();
MethodSignature sig = (MethodSignature) pjp.getSignature();
StandardEvaluationContext ctx = new StandardEvaluationContext();
String[] names = sig.getParameterNames();
Object[] args = pjp.getArgs();
for (int i = 0; i < names.length; i++) {
ctx.setVariable(names[i], args[i]);
}
String bizKey = parser.parseExpression(idempotent.key()).getValue(ctx, String.class);
return "idem:" + sig.getDeclaringTypeName() + ":" + sig.getMethod().getName()
+ ":" + bizKey;
}
}
/** ③ 业务使用:一行注解 */
@Idempotent(key = "#req.userId + ':' + #req.productId", expireSeconds = 15)
@PostMapping("/order")
public OrderVO createOrder(@RequestBody @Valid OrderReq req) {
return orderService.createOrder(req);
}
为什么 key 里要带方法签名:不同接口的防重相互隔离,userId=1 在下单接口和充值接口的防重互不影响。
3.2 真正的原子性:SETNX 单条命令就够,别再自己造“查询+写入”
网上流传的错误写法是先 GET 判断再 SET——GET 和 SET 之间就是并发窗口,两道请求同时 GET 到 null、同时 SET 成功,防重失效。修复就是 3.1 的 setIfAbsent(key, val, timeout):Redis 的 SETNX 语义保证判定和写入是同一个原子操作,两道并发请求只有一个能拿到 true。
同理,删除防重键的时机也不能随手写——这是 3.3 的坑 2。
3.3 防坑要点(每一条都来自真实事故)
| 坑 | 现象 | 对策 |
|---|---|---|
| 坑 1:过期时间 < 业务耗时 | 下单要 12s,防重键 5s 过期 → 第 6s 用户重试穿透;或者更糟:第一单还在处理中、第二单也进来了 | expireSeconds ≥ 最大处理时长的 2 倍;超长流程(如支付回调)配合业务状态机 |
| 坑 2:异常时删不删键是两个语义 | 删 → 业务失败允许立即重试(体验好);不删 → 事务回滚了但键还在,用户在剩余有效期内被误拦 | 删除(如 3.1 代码),把“失败可重试”还给用户;但删除动作要在事务回滚之后发生(切面在事务外层,天然满足) |
| 坑 3:键值设 "1" 无业务含义 | 排查问题时不知道这个键是谁、什么时候放的 | value 存 traceId 或时间戳,排查利器 |
| 坑 4:Redis 挂了怎么办 | 防重层整体失效 → 退化为无幂等 | 降级策略显式化:Redis 异常时放行 + 靠方案一唯一索引兜底(双层防线,见第六章组合拳) |
| 坑 5:集群下的时钟/主从延迟 | 主从切换瞬间 SETNX 结果可能丢失 | 可接受(窗口毫秒级);强一致场景换方案一或方案四 |
3.4 适用场景
默认主力方案:有明确业务唯一键的提交类接口(下单、领券、报名)。它把防重从 DB 挪到 Redis,扛并发能力强(SETNX 单线程原子)、侵入性低(一个注解)、体验可控(失败即返回提示)。
四、方案三:Token 机制——先拿令牌,再提交
4.1 设计动机:没有天然唯一键怎么办
有些接口的业务参数天然不唯一:比如“提交一张表单",两次提交的字段完全一样但语义上就该是两条记录(或用户就是想改了再提交)。这时没有业务键可用,Token 机制的思路是人为造一个一次性凭证:
① 页面加载时:客户端请求 /token → 服务端生成随机 token 存入 Redis,返回给前端
② 用户提交时:请求头带上这个 token
③ 服务端处理:原子删除 token(DEL 返回 1 = 首次)→ 执行业务;返回 0 = 重复提交拒绝
4.2 完整代码
@RestController
@RequiredArgsConstructor
public class TokenController {
private final StringRedisTemplate redis;
/** ① 发放 token:UUID + 存入 Redis(5 分钟有效) */
@GetMapping("/form/token")
public Map<String, String> issueToken() {
String token = UUID.randomUUID().toString();
redis.opsForValue().set("form:token:" + token, "1", 5, TimeUnit.MINUTES);
return Map.of("token", token);
}
/** ② 提交时校验:原子 DEL,删除成功 = 第一次提交 */
@PostMapping("/form/submit")
public Map<String, Object> submit(@RequestHeader("X-Form-Token") String token,
@RequestBody FormReq req) {
// 核心原子性:DEL 是单命令,删除成功与否天然防并发
Boolean first = redis.delete("form:token:" + token);
if (Boolean.FALSE.equals(first)) {
throw new BizException("IDEMPOTENT_REJECT", "表单已提交,请勿重复操作");
}
// token 已核销,安全执行业务
formService.submit(req);
return Map.of("ok", true);
}
}
为什么用 DEL 而不是 GET+DEL:和方案二同理,GET 和 DEL 之间是并发窗口,两道请求同时 GET 到 token 存在、同时执行业务。DEL 的原子性让“核销”这个动作天然只有一次生效。
4.3 防坑要点
| 坑 | 说明 | 对策 |
|---|---|---|
| 坑 1:token 何时核销有讲究 | 先执行业务再核销 → 业务执行期间重复请求穿透;先核销再执行业务(本文做法)→ 业务失败 token 已没了,用户重提交要重新拿 token | 两种顺序选一种并接受其代价:本文选“先核销”,失败的代价是“重新拉一次 token”(体验可接受,一致性更好);核心资金类接口改用方案一/四兜底 |
| 坑 2:前端遗漏传 token | 老版本页面/小程序没带 token → 全被拒绝 | token 校验失败时返回明确的错误码,前端灰度切换;网关层兼容“无 token 走方案二”的过渡期 |
| 坑 3:token 被抓包重放 | token 本身无用户绑定 | token 与 userId 绑定存储(value 存 userId,DEL 时校验),并且全程 HTTPS |
| 坑 4:Redis DEL 前服务重启 | token 丢失 → 用户提交失败 | token 有效期给足 + 前端提供“重新获取”自动重试 |
4.4 适用场景
表单防重复提交(无天然业务键)、文件上传、活动报名这类“页面级一次性操作”。它比方案二多一次交互(先拿 token),换来的是“不依赖业务键设计”——业务键梳理不清楚时,Token 是解耦的答案。
五、方案四:乐观锁版本号——状态更新的幂等
5.1 与前三者的本质区别
前三种防的是“动作别执行两次"(insert 两条、处理两遍),乐观锁防的是“状态别被错误覆盖”——它的主战场是 UPDATE 语句:
// 需求:订单状态 CAS 流转(待支付 → 已支付),防重复回调把状态改乱
// ❌ 朴素写法:查出来改回去(查出 1ms 内状态可能已被并发修改)
Order order = orderMapper.selectById(orderId);
if (order.getStatus() != PENDING) { throw new BizException("状态错误"); }
order.setStatus(PAID); // 中间可能有另一笔回调已改成 PAID
orderMapper.update(order); // 覆盖了别人的修改!
// ✅ 乐观锁:UPDATE 带版本条件,状态机 + 版本号双重保护
int updated = orderMapper.updateStatus(
orderId,
expectedStatus = PENDING, // 期望旧状态
targetStatus = PAID,
expectedVersion = order.getVersion(), // 期望旧版本号
nextVersion = order.getVersion() + 1);
if (updated == 0) {
// 没更新到 = 状态已被别人流转 → 天然幂等:重复回调第二次进来 updated=0,直接返回成功
log.info("订单状态已流转,视为重复回调,幂等返回");
}
UPDATE t_order
SET status = #{targetStatus},
version = version + 1
WHERE id = #{orderId}
AND status = #{expectedStatus} -- 状态机条件
AND version = #{expectedVersion}; -- 版本号条件
乐观锁幂等的妙处:重复请求第二次执行 UPDATE,条件不满足影响行数为 0——它不是“拒绝重复”,而是“重复执行也无害”,这才是幂等最优雅的形态。MyBatis-Plus 的 @Version 注解 + OptimisticLockerInnerInterceptor 可以自动完成版本号读写,不用手写 SQL。
5.2 防坑要点
| 坑 | 说明 | 对策 |
|---|---|---|
| 坑 1:updated=0 就抛异常 | 重复回调被当成错误返回 → 上游重试风暴 | 区分“条件不满足但终态正确”(幂等成功返回)和“真正的冲突”(可重试/告警) |
| 坑 2:重试没有上限 | 高并发下版本冲突率上升,无限重试放大 DB 压力 | 重试 2~3 次 + 退避,仍失败转人工/MQ 削峰 |
| 坑 3:version 忘了自增 | 每次都更新成功,锁形同虚设 | version 自增放 SQL 里(version = version + 1),别在 Java 里 set |
5.3 适用场景
状态流转类接口的唯一正解:支付回调、订单状态机、审批流、库存扣减(配合 stock > 0 条件)。它不能防 insert 重复(新纪录没有版本号),所以和方案一/二是互补关系。
六、选型决策与组合拳
6.1 四方案对比总表
| 维度 | 唯一索引 | Redis SETNX | Token 机制 | 乐观锁 |
|---|---|---|---|---|
| 防护对象 | 重复落库 | 重复执行动作 | 页面重复提交 | 状态错误覆盖 |
| 强一致性 | ✅ 最高 | 中(Redis 可用性内) | 中 | ✅ 最高 |
| 性能开销 | DB 约束检查 | Redis 一次原子操作 | Redis DEL | DB 行锁竞争 |
| 侵入性 | 建表设计期确定 | 一个注解,低 | 前后端配合改造 | UPDATE 语句改造 |
| 并发承载 | 中(DB 锁) | 高 | 高 | 中 |
| 前置条件 | 业务天然唯一键 | 业务可提取唯一键 | 无要求 | 更新类接口 |
| 用户体验 | 重复时异常 | 即时提示 | 需先拿 token | 无感 |
6.2 选型口诀
有天然唯一键 + 数据落库 → 唯一索引兜底 + Redis SETNX 提速(组合拳)
无业务键的页面提交 → Token 机制
状态流转/更新类 → 乐观锁(唯一正解)
拿不准 → Redis SETNX 先上,唯一索引后补
6.3 生产级组合拳:三层防线
真实的下单接口,推荐三层组合(各层各司其职):
@Idempotent(key = "#req.userId + ':' + #req.productId", // 第一层:Redis 防重(快速拒绝 + 体验好)
expireSeconds = 15)
@PostMapping("/order")
public OrderVO createOrder(@RequestBody @Valid OrderReq req) {
return orderService.createOrder(req); // 第二层:事务内唯一索引(DB 兜底)
}
@Service
@Transactional(rollbackFor = Exception.class) // 第三层:状态流转用乐观锁
public OrderVO createOrder(OrderReq req) {
stockMapper.deductWithCas(req.getProductId()); // UPDATE ... stock>0(乐观锁语义)
orderMapper.insert(order); // 撞 uk_user_product_activity 兜底
mqSender.send(orderPaidEvent); // insert 成功后才发 MQ
return toVO(order);
}
防线分工:
Redis SETNX → 99% 的重复请求在进入业务前被快速拒绝(扛并发 + 用户提示)
唯一索引 → Redis 失效/窗口期穿透的最后一道数据防线
乐观锁 → 状态相关的字段不被并发覆盖
这套组合的哲学:任何单一防线都有失效窗口(Redis 主从切换、DB 约束太晚、前端按钮漏配),幂等不追求单点完美,追求纵深防御。
七、常见问题
7.1 幂等键和分布式锁是一回事吗?
不是,但容易混。分布式锁保证“同一时刻只有一个执行者”(互斥,执行完就释放);幂等保证“同一业务请求执行多次效果不变”(防重,记录的是“已处理”事实)。SETNX 只是恰好两者都能实现——锁的 key 语义是“我正在做”,幂等的 key 语义是“我已经做过”。把幂等键当锁用(执行完立刻删),防不住“执行完成后的重放”;把锁当幂等用(一直不释放),又会误拦正常请求。
7.2 幂等过期时间到底设多长?
下限 = 业务最大处理时长 × 2(含下游 RPC、MQ 发送);上限 = “同一业务请求合理的重复间隔”。下单类:15~30 秒足够拦住双击和网络重试;支付回调类:用“业务状态机”代替时间窗(永远幂等);表单类:token 5 分钟。没有万能值,但“小于业务耗时”一定是错的。
7.3 幂等拒绝时应该返回什么 HTTP 状态码?
分场景:防重窗口内重复提交 → 409 Conflict + 业务错误码(语义最准);token 已核销 → 409 或 400;幂等成功但属于重复执行(如重复回调的乐观锁路径)→ 返回 200 + 原业务结果,不要返回错误——上游的重试逻辑会因此风暴。返回什么取决于调用方视角:“你的请求我处理过了,结果如下”是成功,不是失败。
7.4 MQTT/消息消费侧的幂等怎么做?
消息消费的幂等不推荐 SETNX 切面(消费线程池并发 + 重试时序复杂),推荐业务唯一键 + 唯一索引/状态机:消费逻辑以“落库或改状态”为幂等锚点,重复消息撞唯一索引后确认提交(ack),让 MQ 的重试机制与业务幂等解耦。消息侧的防重表(msgId + 消费记录表)是另一种通用解,适合无天然业务键的广播消息。
7.5 前端按钮置灰能替代后端幂等吗?
不能,一票否决。理由:① 前端代码可绕过(直接调 API);② 页面多端并存(小程序/APP/开放接口)难以同步防重逻辑;③ 网络层重试发生在 HTTP 客户端,前端完全无感。前端置灰是体验优化,后端幂等是正确性保证——这句话值得写进团队规范。
7.6 怎么验证幂等做对了?
并发双发测试是最低标准:用 JMeter/wrk 对同一请求参数并发打 100 次,断言“业务结果只产生一次”(订单只有一条、库存只扣一次、MQ 只有事件)。再加两个用例:① 失败重试场景(第一次人为失败,第二次应成功——验证坑 2 的“删键”语义);② 慢处理场景(把下游 mock 慢到超过防重有效期,验证窗口设计)。幂等测试和普通功能测试的区别就是这组并发断言,值得为资金类接口专门写一套。
八、总结
四方案速查卡
┌────────────┬─────────────────────┬────────────────────┬──────────────┐
│ 方案 │ 一句话定位 │ 防坑核心 │ 适用 │
├────────────┼─────────────────────┼────────────────────┼──────────────┤
│ 唯一索引 │ 最后的数据防线 │ 唯一键设计 + 事务包裹 │ 有天然唯一键 │
│ Redis SETNX│ 推荐主力(注解化) │ 原子性 + 异常删键 │ 提交类接口 │
│ Token │ 无业务键的解耦方案 │ 先核销后执行 │ 表单/上传 │
│ 乐观锁 │ 状态流转唯一正解 │ updated=0 的语义 │ UPDATE 类 │
└────────────┴─────────────────────┴────────────────────┴──────────────┘
生产组合拳:SETNX 快速拒绝 → 唯一索引数据兜底 → 乐观锁状态保护
一句话
接口幂等的全部秘密是“找到业务唯一键,在正确的层次用原子操作检查它”:数据库唯一索引是最硬的防线,Redis SETNX 是最快的守门员,Token 是没有业务键时的人造凭证,乐观锁让“重复执行也无害”成为最优雅的形态。单一方案都有失效窗口,生产级的答案是纵深防御——99% 的重复请求被 Redis 拒之门外,漏网之鱼撞死在唯一索引上,状态字段由乐观锁守到最后。幂等不是选一道题,而是布一个局。
给团队的建议
| 项 | 建议 |
|---|---|
| 规范 | 所有 POST 提交类接口默认配 @Idempotent 注解(SETNX 方案),Code Review 必查 |
| 设计期 | 建表时就想清楚业务唯一键——唯一索引是免费送的幂等,事后加才痛 |
| 组合 | 资金/库存类接口三层防线齐配;普通表单 Token 或 SETNX 单层即可 |
| 语义 | 团队统一幂等拒绝的错误码与 HTTP 状态(409 + 业务码),前端统一处理 |
| 测试 | 资金类接口必须配“并发双发 100 次 + 失败重试 + 慢处理”三件套用例 |
互动话题:你被重复提交坑过最惨的一次是什么场景?你们的幂等键是怎么设计的?评论区聊聊。
参考资料
- RFC 9110:HTTP 语义(幂等方法定义)
- Redis 官方:SET NX 与事务原子性
- Spring Data Redis 官方文档
- MyBatis-Plus:乐观锁插件(@Version)
- MySQL 官方:唯一索引与重复键处理
- Spring AOP 官方文档(切面顺序)
- SpEL 表达式官方文档
标题:Spring Boot 3.x 优雅实现接口幂等:4 种方案一次讲透
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/09/11/1788597582453.html
公众号:服务端技术精选
- 引言
- 一、为什么需要幂等:不只是双击
- 1.1 重复请求的四个来源
- 1.2 幂等的本质:业务唯一键 + 执行前检查
- 二、方案一:唯一索引——最可靠的兜底
- 2.1 实现:靠数据库约束兜底
- 2.2 适用场景与定位
- 2.3 防坑要点
- 三、方案二:Redis SETNX + 过期时间——推荐主力(完整代码)
- 3.1 设计:防重令牌 = 业务前缀 + 唯一标识 + 有效期
- 3.2 真正的原子性:SETNX 单条命令就够,别再自己造“查询+写入”
- 3.3 防坑要点(每一条都来自真实事故)
- 3.4 适用场景
- 四、方案三:Token 机制——先拿令牌,再提交
- 4.1 设计动机:没有天然唯一键怎么办
- 4.2 完整代码
- 4.3 防坑要点
- 4.4 适用场景
- 五、方案四:乐观锁版本号——状态更新的幂等
- 5.1 与前三者的本质区别
- 5.2 防坑要点
- 5.3 适用场景
- 六、选型决策与组合拳
- 6.1 四方案对比总表
- 6.2 选型口诀
- 6.3 生产级组合拳:三层防线
- 七、常见问题
- 7.1 幂等键和分布式锁是一回事吗?
- 7.2 幂等过期时间到底设多长?
- 7.3 幂等拒绝时应该返回什么 HTTP 状态码?
- 7.4 MQTT/消息消费侧的幂等怎么做?
- 7.5 前端按钮置灰能替代后端幂等吗?
- 7.6 怎么验证幂等做对了?
- 八、总结
- 四方案速查卡
- 一句话
- 给团队的建议
- 参考资料
评论