← 返回首页
🛡️

幂等设计

📂 architecture ⏱ 2 min 257 words

幂等设计

幂等性的定义

幂等性是指同一个操作执行一次和执行多次产生的效果相同。在分布式系统中,网络重试、消息重复投递等场景都需要幂等性保证。

非幂等操作示例:
  扣减库存: 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);
    }
}

幂等设计需要根据业务场景选择合适的实现方式,核心思想是通过去重或状态控制确保重复操作的安全性。