接口幂等性

接口幂等性是分布式系统设计中的一个重要概念,指的是无论调用多少次,相同请求产生的结果都是一致的。这是保证系统可靠性的关键设计原则。

🔍 幂等性的核心概念

什么是幂等性?

  • 数学定义:$f(f(x)) = f(x)$
  • 系统定义:同一操作的多次执行与一次执行效果相同
  • 简单理解:第一次请求产生的影响,后续重复请求不会产生额外影响

🎯 为什么需要幂等性?

常见场景

  1. 前端重复提交
  2. 消息队列重复消费
  3. 接口超时重试
  4. 分布式系统网络抖动
  5. 第三方回调重试

⚡ 实现幂等性的常用方案

1. Token 令牌机制

java

// 生成唯一token,提交时验证并删除
public boolean checkToken(String token) {
return redis.delete("idempotent:token:" + token) > 0;
}

2. 数据库唯一约束

sql

CREATE TABLE orders (
id BIGINT PRIMARY KEY,
order_no VARCHAR(32) UNIQUE, -- 唯一业务流水号
status INT,
...
);

3. 乐观锁(版本号控制)

sql

UPDATE account 
SET balance = balance - 100,
version = version + 1
WHERE id = 123 AND version = 1;

4. 状态机幂等

java

// 状态流转检查
public boolean updateOrderStatus(Long orderId, OrderStatus current, OrderStatus target) {
return executeUpdate(
"UPDATE order SET status = ? WHERE id = ? AND status = ?",
target, orderId, current
) > 0;
}

5. 分布式锁

java

@Idempotent(key = "#order.orderNo")
public Result createOrder(Order order) {
// 业务逻辑
}

🛠️ 不同 HTTP 方法的幂等性

方法是否幂等说明
GET✅ 是读取操作,不影响数据
PUT✅ 是更新资源,重复操作结果不变
DELETE✅ 是删除后资源不存在
POST❌ 否创建新资源,每次调用产生新数据
PATCH⚠️ 不一定取决于具体实现

📋 幂等性设计模式

方案对比表

方案适用场景优点缺点
Token 机制表单提交、页面操作简单有效需前后端配合
唯一索引创建类操作、流水号数据库层面保证依赖数据库
乐观锁更新类操作轻量级,性能好需设计版本字段
状态机状态流转类操作业务逻辑清晰需合理设计状态
分布式锁高并发场景强一致性性能开销较大

🔧 最佳实践

通用实现框架

java

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
String key() default ""; // 幂等键
long expire() default 3600; // 过期时间
String message() default "请勿重复操作";
}

// AOP 实现
@Component
@Aspect
public class IdempotentAspect {
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) {
String key = generateKey(idempotent.key(), joinPoint);

if (!redis.setIfAbsent(key, "1", idempotent.expire())) {
throw new RepeatRequestException(idempotent.message());
}

try {
return joinPoint.proceed();
} finally {
// 可选:业务完成后删除key
}
}
}

注意事项

  1. 幂等键设计要合理:包含业务唯一标识
  2. 设置合适的过期时间:避免永久占用存储
  3. 区分首次和重复请求:给客户端明确的响应
  4. 考虑并发场景:使用原子操作
  5. 异常处理:网络异常、系统崩溃等场景

🌰 实际应用示例

支付回调幂等处理

java

public void handlePaymentCallback(PaymentNotify notify) {
String idempotentKey = "payment:" + notify.getTradeNo();

// 1. 检查是否已处理
if (redis.exists(idempotentKey)) {
log.info("重复回调,直接返回成功");
return Response.success();
}

// 2. 获取分布式锁
String lockKey = "lock:" + idempotentKey;
if (!tryLock(lockKey)) {
throw new BusinessException("处理中,请稍后");
}

try {
// 3. 查询订单当前状态
Order order = orderDao.selectByTradeNo(notify.getTradeNo());

// 4. 状态检查(幂等)
if (order.getStatus() == OrderStatus.PAID) {
log.info("订单已支付,幂等返回");
return;
}

// 5. 执行业务逻辑
processPayment(order, notify);

// 6. 设置幂等标记
redis.setex(idempotentKey, 24 * 3600, "1");

} finally {
releaseLock(lockKey);
}
}

📊 监控与维护

  1. 幂等冲突监控:记录重复请求日志
  2. 存储清理:定期清理过期幂等键
  3. 性能监控:关注分布式锁和Redis性能
  4. 告警机制:异常幂等率告警

总结:接口幂等性是分布式系统设计的基石之一。合理选择幂等方案,能显著提升系统的可靠性和用户体验。建议在业务设计初期就考虑幂等性问题,避免后期重构带来的复杂性和风险。