一句话总结
幂等性核心:同一请求多次调用对系统影响与一次相同。方案:Token机制防重复提交、唯一索引防重复插入、状态机防重复操作、版本号防并发更新。
初级理解
幂等性定义:同一请求多次调用,对系统产生的影响与调用一次完全相同。
常见设计方案:
1. Token机制:服务端生成Token存入Redis,客户端提交时携带,验证后删除
2. 数据库唯一索引:利用订单号等唯一约束,重复插入直接报错
3. 状态机:如订单状态只能单向流转,重复请求被拒绝
4. 版本号:UPDATE SET version=version+1 WHERE id=? AND version=?
一句话总结:Token防重复提交,唯一索引防重复插入,状态机防重复操作。
中级深入
Token机制实现:
// 1. 生成Token
@GetMapping("/token")
public String generateToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("token:" + token, "1", 5, TimeUnit.MINUTES);
return token;
}
// 2. 验证Token
@PostMapping("/submit")
public Result submit(@RequestParam String token, @RequestBody OrderDTO order) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = redisTemplate.execute(script, List.of("token:" + token), token);
if (result == 1) {
return orderService.createOrder(order);
} else {
return Result.fail("请勿重复提交");
}
}
注意:Token必须设置过期时间,防止用户长期不提交导致Token失效。
高级拓展
分布式环境下的幂等性:
public Result createOrder(OrderDTO order) {
String lockKey = "order:lock:" + order.getOrderNo();
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (!locked) return Result.fail("请勿重复提交");
// 数据库唯一索引兜底
Order existing = orderMapper.selectByOrderNo(order.getOrderNo());
if (existing != null) return Result.fail("订单已存在");
orderMapper.insert(order);
redisTemplate.delete(lockKey);
return Result.success("创建成功");
}
消息队列的幂等性:
@RocketMQMessageListener(topic = "order-topic", consumerGroup = "group")
public void onMessage(OrderMessage message) {
String key = "processed:" + message.getMessageId();
if (redisTemplate.hasKey(key)) return; // 已消费
orderService.processOrder(message);
redisTemplate.opsForValue().set(key, "1", 24, TimeUnit.HOURS);
}
面试加分项:能说出分布式环境下的幂等性方案、消息队列的幂等性处理,说明你对分布式系统有深入理解。
实战场景
场景:下单接口Token幂等
// 流程:
// 1. 用户进入下单页面,前端请求/api/token获取Token
// 2. 用户点击下单,请求体携带该Token
// 3. 后端通过Lua脚本原子性地检查并删除Redis中的Token
// 4. 若删除成功,执行业务逻辑;若Token不存在,返回"请勿重复提交"
// 5. 即使网络重试,第二次请求因Token已被消费,保证订单只创建一次
场景:支付回调幂等
@Transactional
public void handlePaymentCallback(PaymentDTO payment) {
Order order = orderMapper.selectByOrderNo(payment.getOrderNo());
// 状态机校验
if (!"PENDING".equals(order.getStatus())) {
log.info("订单{}状态为{},忽略重复支付回调", payment.getOrderNo(), order.getStatus());
return;
}
int updated = orderMapper.updateStatus(order.getId(), "PENDING", "PAID");
if (updated == 0) throw new RuntimeException("状态更新失败");
orderService.afterPayment(order);
}
面试模拟
Q:如何保证分布式系统中的幂等性?
A:综合方案:1. 唯一请求ID:每个请求携带唯一ID,服务端检查是否已处理;2. 分布式锁:防止并发重复处理;3. 数据库唯一索引:数据库层面兜底;4. 状态机:业务状态单向流转。
Q:Token机制有什么缺点?
A:缺点:1. 需要额外存储Token;2. Token有有效期;3. 不能防止恶意重复提交(需配合频率限制);4. 分布式环境下需要保证Token的原子性操作。