幂等设计
幂等设计
幂等性的定义
幂等性是指同一个操作执行一次和执行多次产生的效果相同。在分布式系统中,网络重试、消息重复投递等场景都需要幂等性保证。
非幂等操作示例:
扣减库存: UPDATE stock SET count = count - 1
执行2次: stock 减少2(错误)
幂等操作示例:
扣减库存: UPDATE stock SET count = count - 1 WHERE order_id = 'xxx'
执行2次: stock 只减少1(正确,因为第二次 WHERE 条件不满足)
Token 去重表
Token 机制是最常用的幂等实现方式。客户端在发起请求前先获取一个唯一 Token,服务端处理请求时验证并记录 Token。
@Component
public class IdempotentService {
private final RedisTemplate<String, String> redisTemplate;
public boolean tryAcquireToken(String token) {
// SET NX: 只有 key 不存在时才设置成功
Boolean success = redisTemplate.opsForValue()
.setIfAbsent("idempotent:" + token, "1", 24, TimeUnit.HOURS);
return Boolean.TRUE.equals(success);
}
public void processOrder(String token, OrderRequest request) {
if (!tryAcquireToken(token)) {
throw new DuplicateRequestException("重复请求");
}
try {
orderService.createOrder(request);
} catch (Exception e) {
// 失败时删除 Token,允许重试
redisTemplate.delete("idempotent:" + token);
throw e;
}
}
}
// 客户端使用
@RestController
public class OrderController {
@PostMapping("/orders")
public Order createOrder(@RequestBody OrderRequest request,
@RequestHeader("X-Idempotent-Token") String token) {
return idempotentService.processOrder(token, request);
}
}
数据库唯一约束
利用数据库的唯一约束实现幂等性。通过在业务表中添加唯一键,重复插入会自动失败。
-- 订单表包含幂等键
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) UNIQUE NOT NULL,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 幂等插入
INSERT INTO orders (order_no, user_id, amount, status)
VALUES ('ORD20240101001', 12345, 99.99, 'CREATED')
ON DUPLICATE KEY UPDATE id = id;
状态机实现
通过状态机控制业务流程,确保每个状态只能被触发一次。
// 订单状态机
public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
COMPLETED,
CANCELLED;
}
@Component
public class OrderStateMachine {
private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = Map.of(
CREATED, Set.of(PAID, CANCELLED),
PAID, Set.of(SHIPPED, CANCELLED),
SHIPPED, Set.of(COMPLETED),
COMPLETED, Set.of(),
CANCELLED, Set.of()
);
public void transition(Order order, OrderStatus targetStatus) {
Set<OrderStatus> allowed = TRANSITIONS.get(order.getStatus());
if (!allowed.contains(targetStatus)) {
throw new InvalidTransitionException(
"不允许从 " + order.getStatus() + " 转换到 " + targetStatus);
}
order.setStatus(targetStatus);
orderRepository.save(order);
}
}
幂等设计需要根据业务场景选择合适的实现方式,核心思想是通过去重或状态控制确保重复操作的安全性。