幂等性是指对同一个操作执行一次或多次,产生的结果是相同的。在分布式系统、网络请求和金融交易等场景中,幂等性设计至关重要。下面我将介绍几种常见的幂等性处理方案及其实战示例。
原理:为每个操作分配唯一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);
}
}
原理:通过版本号或条件判断实现更新操作的幂等性。
实现示例:
// 数据库表设计
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");
}
}
}
原理:通过定义业务状态流转规则,确保只有符合条件的状态才能进行转换。
实现示例:
// 订单状态枚举
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);
// 执行取消后的业务逻辑...
}
}
原理:使用单独的表记录已处理的操作,防止重复处理。
实现示例:
// 数据库表设计
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;
}
}
原理:客户端先获取令牌,服务端验证令牌有效性后处理请求。
实现示例:
// 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;
}
}
原理:通过消息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);
}
}
}
幂等性设计是构建健壮分布式系统的关键要素,合理运用这些方案可以显著提高系统的可靠性和一致性。