Spring Boot 3.x微服务接口幂等性实现:从原理到分布式锁方案



微服务架构中,接口幂等性是保证数据一致性的基础要求。网络超时导致的重试、前端重复提交、消息队列重复消费等场景,都可能使同一请求被执行多次。如果接口不具备幂等性,重复执行会产生脏数据或资金错误。Spring Boot 3.x中实现接口幂等性有多种方案,需根据业务场景选择。

接口幂等性原理与适用场景分析

幂等性指同一操作执行一次与多次的效果相同。HTTP协议中GET、PUT、DELETE天然幂等,POST通常不是。但业务层面,即便是PUT请求,若业务逻辑包含非幂等操作(如扣减库存后再加积分),仍需额外保障。

需要幂等保障的典型场景:

– 支付接口:重复扣款是严重事故
– 订单创建:重复下单导致库存被多占
– 状态变更:审批流重复流转
– 消息消费:MQ重复投递导致业务重复执行

判断标准:接口是否产生副作用(写操作),且副作用是否可安全重复。

基于Token令牌的防重复提交方案

Token方案适合表单提交场景。服务端在页面加载时生成一次性Token,提交时校验并销毁:

@RestController
@RequestMapping('/api/token')
public class TokenController {
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping('/generate')
public Result generateToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set('idempotent:' + token, '1', Duration.ofMinutes(30));
return Result.ok(token);
}
public boolean checkAndRemoveToken(String token) {
String key = 'idempotent:' + token;
Boolean deleted = redisTemplate.delete(key);
return Boolean.TRUE.equals(deleted);
}
}

自定义注解标注需要幂等校验的接口,AOP切面实现校验逻辑:拦截请求Header中的Token,校验通过后删除Token,校验失败则拒绝请求。Token方案的局限在于需前端配合,且只能防表单重复提交,无法解决MQ重复消费和超时重试场景。

Redis分布式锁实现幂等性控制

分布式锁方案以请求的唯一标识(业务ID)为锁键,确保同一业务操作同时只有一个在执行:

@Component
public class IdempotentExecutor {
@Autowired
private StringRedisTemplate redisTemplate;
public T execute(String businessKey, long expireSeconds, Supplier action) {
String lockKey = 'idempotent:lock:' + businessKey;
Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, '1', Duration.ofSeconds(expireSeconds));
if (Boolean.FALSE.equals(acquired)) {
throw new BusinessException('操作正在处理中,请勿重复提交');
}
try { return action.get(); }
catch (Exception e) { redisTemplate.delete(lockKey); throw e; }
}
}

支付接口使用示例:

@Service
public class PaymentService {
@Autowired
private IdempotentExecutor idempotentExecutor;
public PaymentResult pay(PaymentRequest request) {
return idempotentExecutor.execute('pay:' + request.getOrderId(), 300, () -> doPayment(request));
}
}

这个方案的关键设计是:成功执行后锁自然过期(默认5分钟),防止短时间重复请求;失败时立即释放锁允许重试。过期时间需大于业务最长处理时间。

数据库唯一约束兜底保障最终一致性

Redis锁在极端情况下(Redis主从切换、网络分区)可能失效,数据库唯一约束是最后防线。对业务唯一键建立唯一索引:

ALTER TABLE payment_record ADD UNIQUE INDEX uk_order_id (order_id);

代码层捕获唯一约束冲突作为幂等返回:

@Transactional
public PaymentResult payWithDbGuard(PaymentRequest request) {
try {
PaymentRecord record = new PaymentRecord();
record.setOrderId(request.getOrderId());
paymentRecordMapper.insert(record);
return doPayment(request);
} catch (DuplicateKeyException e) {
return queryExistingResult(request.getOrderId());
}
}

三层幂等防护形成纵深防御:Token校验拦截前端重复提交,分布式锁防并发请求穿透,数据库唯一约束兜底保证数据层最终一致。各层各司其职,即便单层失效也不会产生脏数据。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3x-wei-fu-wu-jie-kou-mi-deng-xing-shi-xian-cong/

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

相关推荐