什么是幂等?
在一段时间内,对同一接口发起多次相同的请求,无论调用多少次,都应该产生相同的影响或结果,并且不会因为多次请求而改变系统的状态。也就是说,接口幂等性强调的是请求的可重复性和结果的一致性,而不是简单的时间上的“同时间发起”。
在商城下单场景中,确实需要实现接口幂等性来防止因网络重试或其他异常情况导致的订单重复创建等问题。若不实现幂等性控制,用户可能会因为同一笔订单提交了多次请求,进而产生多笔订单,这显然是不符合业务需求的。
解决方案
-
唯一标识符:
- 使用全局唯一ID作为请求标识,例如事务ID或请求流水号,确保每个操作都有一个唯一的参考键。在处理请求时,通过检查数据库中是否已存在此标识符来决定是否执行操作。
-
数据库约束:
- 设置唯一索引,如在订单表中对订单号做唯一约束,从而避免同一订单被创建多次。
- 在更新操作中,结合条件更新语句,只有当满足特定条件时才会更新数据,以保证即使收到多次相同的更新请求,也只会执行一次有效更新。
-
乐观锁/悲观锁:
- 悲观锁:在处理请求前获取资源锁,确保同一时间内只有一个请求能够修改数据。
- 乐观锁:记录数据版本号,在更新时检查版本号是否一致,以防止并发更新导致的数据不一致。
-
业务状态校验:
- 在执行操作之前或之后,验证业务对象的状态,如果发现状态已经改变到不应该再次执行该操作的情况,则拒绝执行。
-
Token机制或滑动窗口:
- 对于短时间内连续的重复请求,可以通过发放Token或者使用滑动窗口机制限制请求频率,超出频率范围的请求直接拒绝。
-
异步处理与幂等消费:
- 将非幂等的操作放入消息队列,确保消息队列消费者具备幂等消费的能力,即无论同一个消息被消费多少次,其结果都是一致的。
-
事务补偿:
- 如果发生了重复操作,可以通过回滚或者补偿交易的方式来恢复业务的一致性。
-
客户端控制:
- 前端可以采取防抖(debounce)或节流(throttle)的方式防止用户的快速重复点击,从而减少重复请求的发出。
示例代码
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final StringRedisTemplate redisTemplate;
public OrderService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 假设这是一个创建订单的方法,需要实现幂等性
public boolean createOrder(OrderRequest request, String uniqueKeyForRequest) {
// 生成一个用于幂等控制的唯一键,这里可能是订单ID或者其他唯一标识
String key = "order:action:unique:" + uniqueKeyForRequest;
// 使用Redis SETNX命令原子性地设置键值,只有当键不存在时才设置成功
Boolean setSuccess = redisTemplate.opsForValue().setIfAbsent(key, "processing", 60, TimeUnit.MINUTES);
if (setSuccess) {
try {
// 执行创建订单的核心逻辑
Order order = processOrder(request);
// 订单创建成功,无需额外处理
return true;
} catch (Exception e) {
// 订单创建失败,可能需要记录错误并进行后续处理
log.error("Order creation failed.", e);
} finally {
// 创建失败或成功后,都要从Redis中移除幂等键,以释放资源
redisTemplate.delete(key);
}
} else {
// 如果键已存在,说明该请求已经被处理过了,返回幂等处理结果
log.info("Duplicate order creation request detected and ignored.");
}
return false;
}
private Order processOrder(OrderRequest request) {
// 实际处理订单创建的逻辑...
// 这里仅作占位,真实情况下会有数据库操作等步骤
return new Order(...);
}
}
在这个例子中,我们使用Redis的SETIFABSENT
命令来保证一个请求的唯一性处理。在处理订单创建请求之前,尝试在Redis中设置一个唯一键,如果键已经存在,则表示该请求已被处理过,不再执行核心逻辑;如果键不存在,则设置成功并执行创建订单操作,无论成功与否,最后都会移除这个幂等键。这样就能有效地防止同一订单请求的重复处理。