幂等性处理解决方案实战示例

幂等性处理解决方案实战示例

幂等性是指对同一个操作执行一次或多次,产生的结果是相同的。在分布式系统、网络请求和金融交易等场景中,幂等性设计至关重要。下面我将介绍几种常见的幂等性处理方案及其实战示例。

1. 唯一标识符方案

原理:为每个操作分配唯一ID,服务端通过检查该ID是否已处理过来保证幂等性。

实现示例

// 数据库表设计
CREATE TABLE idempotent_requests (
    id VARCHAR(64) PRIMARY KEY,
    user_id BIGINT NOT NULL,
    operation_type VARCHAR(32) NOT NULL,
    status VARCHAR(16) NOT NULL,
    result TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

// Java实现示例
@RestController
@RequestMapping("/orders")
public class OrderController {
    
    @Autowired
    private IdempotentRequestRepository idempotentRepo;
    
    @PostMapping
    public ResponseEntity<?> createOrder(
        @RequestHeader("X-Idempotency-Key") String idempotencyKey,
        @RequestBody OrderRequest request) {
        
        // 检查是否已处理过该请求
        Optional<IdempotentRequest> existing = idempotentRepo.findById(idempotencyKey);
        if (existing.isPresent()) {
            // 返回之前的结果
            return ResponseEntity.ok(existing.get().getResult());
        }
        
        // 处理业务逻辑
        Order order = orderService.createOrder(request);
        
        // 保存幂等记录
        IdempotentRequest idempotentRequest = new IdempotentRequest();
        idempotentRequest.setId(idempotencyKey);
        idempotentRequest.setOperationType("CREATE_ORDER");
        idempotentRequest.setStatus("COMPLETED");
        idempotentRequest.setResult(order.toJson());
        idempotentRepo.save(idempotentRequest);
        
        return ResponseEntity.ok(order);
    }
}

2. 乐观锁方案

原理:通过版本号或条件判断实现更新操作的幂等性。

实现示例

// 数据库表设计
CREATE TABLE accounts (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    balance DECIMAL(19,4) NOT NULL,
    version INT NOT NULL DEFAULT 0
);

// Java实现示例
@Service
public class AccountService {
    
    @Autowired
    private AccountRepository accountRepo;
    
    @Transactional
    public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        Account fromAccount = accountRepo.findById(fromAccountId)
            .orElseThrow(() -> new AccountNotFoundException(fromAccountId));
        
        Account toAccount = accountRepo.findById(toAccountId)
            .orElseThrow(() -> new AccountNotFoundException(toAccountId));
        
        if (fromAccount.getBalance().compareTo(amount) < 0) {
            throw new InsufficientBalanceException();
        }
        
        // 使用版本号实现乐观锁
        int updatedRows = accountRepo.decreaseBalance(
            fromAccountId, amount, fromAccount.getVersion());
        
        if (updatedRows == 0) {
            throw new OptimisticLockingFailureException("Account update failed");
        }
        
        updatedRows = accountRepo.increaseBalance(
            toAccountId, amount, toAccount.getVersion());
        
        if (updatedRows == 0) {
            throw new OptimisticLockingFailureException("Account update failed");
        }
    }
}

3. 状态机方案

原理:通过定义业务状态流转规则,确保只有符合条件的状态才能进行转换。

实现示例

// 订单状态枚举
public enum OrderStatus {
    CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}

// 订单服务
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepo;
    
    @Transactional
    public void cancelOrder(Long orderId) {
        Order order = orderRepo.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
        
        // 状态检查确保幂等性
        if (order.getStatus() == OrderStatus.CANCELLED) {
            return; // 已经取消,直接返回
        }
        
        // 只有特定状态可以取消
        if (order.getStatus() != OrderStatus.CREATED 
            && order.getStatus() != OrderStatus.PAID) {
            throw new IllegalStateException("Order cannot be cancelled in current state");
        }
        
        order.setStatus(OrderStatus.CANCELLED);
        orderRepo.save(order);
        
        // 执行取消后的业务逻辑...
    }
}

4. 去重表方案

原理:使用单独的表记录已处理的操作,防止重复处理。

实现示例

// 数据库表设计
CREATE TABLE payment_records (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id BIGINT NOT NULL,
    payment_id VARCHAR(64) NOT NULL,
    amount DECIMAL(19,4) NOT NULL,
    status VARCHAR(16) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_order_payment (order_id, payment_id)
);

// Java实现示例
@Service
public class PaymentService {
    
    @Autowired
    private PaymentRecordRepository paymentRecordRepo;
    
    @Transactional
    public PaymentResult processPayment(PaymentRequest request) {
        // 检查是否已处理过该支付
        Optional<PaymentRecord> existing = paymentRecordRepo
            .findByOrderIdAndPaymentId(request.getOrderId(), request.getPaymentId());
        
        if (existing.isPresent()) {
            return existing.get().toResult();
        }
        
        // 处理支付逻辑
        PaymentResult result = paymentGateway.charge(request);
        
        // 保存支付记录
        PaymentRecord record = new PaymentRecord();
        record.setOrderId(request.getOrderId());
        record.setPaymentId(request.getPaymentId());
        record.setAmount(request.getAmount());
        record.setStatus(result.getStatus());
        paymentRecordRepo.save(record);
        
        return result;
    }
}

5. Token机制方案

原理:客户端先获取令牌,服务端验证令牌有效性后处理请求。

实现示例

// Java实现示例
@RestController
@RequestMapping("/api")
public class ApiController {
    
    @Autowired
    private TokenService tokenService;
    
    // 获取令牌
    @GetMapping("/token")
    public ResponseEntity<TokenResponse> getToken() {
        String token = tokenService.generateToken();
        return ResponseEntity.ok(new TokenResponse(token));
    }
    
    // 提交订单(需要验证令牌)
    @PostMapping("/order")
    public ResponseEntity<Order> createOrder(
        @RequestParam("token") String token,
        @RequestBody OrderRequest request) {
        
        // 验证并消费令牌
        if (!tokenService.consumeToken(token)) {
            throw new InvalidTokenException();
        }
        
        // 处理业务逻辑
        Order order = orderService.createOrder(request);
        return ResponseEntity.ok(order);
    }
}

@Service
public class TokenService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // 令牌有效期
    private static final long TOKEN_EXPIRE_SECONDS = 300;
    
    public String generateToken() {
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(
            "token:" + token, "1", TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS);
        return token;
    }
    
    public boolean consumeToken(String token) {
        String key = "token:" + token;
        // 使用原子操作删除令牌,防止并发问题
        Long deleted = redisTemplate.delete(key);
        return deleted != null && deleted > 0;
    }
}

6. 消息队列幂等消费

原理:通过消息ID或业务唯一标识实现消息的幂等消费。

RocketMQ实现示例

// Java消费者示例
@Component
@RocketMQMessageListener(
    topic = "ORDER_PAYMENT_TOPIC",
    consumerGroup = "ORDER_PAYMENT_GROUP")
public class OrderPaymentConsumer implements RocketMQListener<MessageExt> {
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private ConsumedMessageRepository consumedMessageRepo;
    
    @Override
    public void onMessage(MessageExt message) {
        String messageId = message.getMsgId();
        String orderId = new String(message.getBody());
        
        // 检查是否已处理过该消息
        if (consumedMessageRepo.existsByMessageId(messageId)) {
            return; // 已处理,直接返回
        }
        
        try {
            // 处理支付逻辑
            paymentService.processPayment(orderId);
            
            // 记录已消费消息
            ConsumedMessage record = new ConsumedMessage();
            record.setMessageId(messageId);
            record.setOrderId(orderId);
            record.setConsumedTime(new Date());
            consumedMessageRepo.save(record);
        } catch (Exception e) {
            // 处理异常,可能需要重试
            throw new RuntimeException(e);
        }
    }
}

最佳实践建议

  1. 根据业务场景选择方案:不同的业务场景适合不同的幂等性方案
  2. 组合使用多种方案:复杂场景可以组合使用多种幂等性方案
  3. 考虑性能影响:幂等性检查可能带来性能开销,需要权衡
  4. 合理设置有效期:对于临时性幂等控制,设置合理的过期时间
  5. 完善的日志记录:记录幂等性检查和处理过程,便于排查问题
  6. 客户端配合:让客户端参与幂等性设计,如生成唯一请求ID

幂等性设计是构建健壮分布式系统的关键要素,合理运用这些方案可以显著提高系统的可靠性和一致性。

你可能感兴趣的:(架构总结,分布式,架构,安全架构)