Spring Boot接口幂等设计实战:从防重复提交到分布式锁

幂等性是高并发系统的核心要求:同一请求无论执行一次还是多次,结果一致且不产生副作用。支付回调、订单创建、消息重试,这些场景一旦缺少幂等保护,重复提交会造成重复扣款、重复下单、脏数据。本文从问题模型出发,介绍Spring Boot项目里几种可落地的幂等方案:前端防重、唯一键约束、Redis锁、数据库乐观锁,以及它们各自的适用边界。

接口幂等的定义与判断方法

接口幂等指客户端用相同参数重复请求,服务端状态不发生重复变化。判断一个接口是否需要幂等:看它是否涉及写操作、是否可能被重试(客户端超时重试、消息队列重投、支付平台回调)。读接口天然幂等,写接口按影响面决定采用哪种方案。幂等设计需要覆盖三个环节:请求入口、业务执行、数据落地,任何一个环节漏掉,重复请求都可能穿透。

方案一:前端防重复提交

最简单的一层:按钮提交后置灰、限制重复点击。前端方案能挡住用户手抖,但挡不住程序重试。实现时注意,防重复只在前端生效,服务端必须有自己的兜底:

@PostMapping("/order/create")
public Result createOrder(@RequestBody OrderDTO dto) {
    // 1. 前端已提交唯一请求号,服务端仍要校验
    if (idempotentService.hasProcessed(dto.getRequestId())) {
        return Result.of(Code.REPEATED, "重复请求");
    }
    // 2. 执行业务
    Order order = orderService.create(dto);
    return Result.ok(order);
}

requestId由前端生成,后端用Redis setnx记录已处理,这是最常用的第一道防线。

方案二唯一键约束兜底

前端令牌和Redis锁都可能因故障失效,数据库唯一约束是最硬的兜底。对支付回调、下单这类强约束场景,在表上加业务唯一键:

-- 订单表:同一商户订单号只允许一次成功支付
ALTER TABLE payment_record
  ADD UNIQUE KEY uk_biz_no (biz_no, payment_channel);

写入时捕获DuplicateKeyException,转为”重复请求”提示,而不是直接报500。唯一键方案依赖数据库,性能低于Redis,但可靠性最高,适合关键资金类场景。

方案三Redis分布式锁控制并发

并发场景(同一用户同时提交两笔订单)需要分布式锁保证临界区串行。Redis setnx + 过期时间实现:

public boolean tryLock(String key, String requestId, long expireSec) {
    Boolean ok = stringRedisTemplate.opsForValue()
        .setIfAbsent(key, requestId, Duration.ofSeconds(expireSec));
    return Boolean.TRUE.equals(ok);
}

@PostMapping("/withdraw")
public Result withdraw(@RequestBody WithdrawDTO dto) {
    String lockKey = "lock:withdraw:" + dto.getUserId();
    if (!tryLock(lockKey, UUID.randomUUID().toString(), 10)) {
        return Result.error("操作处理中,请勿重复提交");
    }
    try {
        return withdrawService.doWithdraw(dto);
    } finally {
        // 释放锁前校验value,防止误删他人锁
        stringRedisTemplate.delete(lockKey);
    }
}

锁的value要带请求标识,释放时先比对再删除,避免超时后误删别的请求的锁。高并发写场景下,锁粒度要小,能锁用户维度的不要锁全局。

幂等方案选型与常见坑

四层方案的选用顺序建议:前端防重复 → 服务层幂等标记 → Redis锁 → 数据库唯一约束,按业务重要性叠加。常见坑有三个:幂等标记与业务事务不在同一原子边界,回滚后标记已写入导致后续请求全部拒绝;Redis锁过期时间短于业务执行时间,锁提前释放造成并发穿透;重试请求参数不一致,幂等判断失效。设计幂等时要专门梳理这三个边界,并在测试用例里覆盖”重复请求””并发同key””锁超时”三类场景。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-jie-kou-mi-deng-she-ji-shi-zhan-cong-fang-chong/

(0)
小编小编
上一篇 4小时前
下一篇 4小时前

相关推荐