接口幂等性(Idempotency)

一、什么是接口幂等性?------ 原理篇

幂等性(Idempotence):一个操作无论执行一次还是多次,其结果都完全相同,不会产生副作用。

在 Web 接口层面,这意味着:

对同一个请求(含相同参数、用户、业务标识等),重复调用 N 次,和调用 1 次的效果一致。

数学类比(帮助理解):

  • 幂等函数:f(f(x)) = f(x)
    例如:abs(abs(-5)) = abs(-5) = 5
  • 非幂等函数:f(x) = x + 1f(f(1)) = 3 ≠ f(1) = 2

接口幂等 ≠ HTTP 方法幂等

  • HTTP 标准中,GET/PUT/DELETE 是语义幂等的,但业务实现可能破坏幂等性
  • 例如:DELETE /order/123 如果每次删除都记录日志并扣减库存,就不是真正幂等。

关键结论 :幂等性必须由业务逻辑 + 数据设计共同保障,不能依赖 HTTP 方法语义。

二、为什么需要幂等性?------ 使用场景篇

以下场景极易引发重复请求,必须做幂等控制:

场景 说明
网络超时重试 客户端未收到响应,自动重发请求(如 Feign、Dubbo 重试)
用户重复点击 支付按钮连点、表单重复提交
浏览器刷新/后退 POST 请求刷新导致重复提交
消息队列重复消费 Kafka/RocketMQ 至少一次投递语义
定时任务重叠执行 分布式调度框架(如 XXL-JOB)任务重入

⚠️ 若不做幂等,后果严重:重复下单、重复扣款、库存超卖、数据错乱。

三、Spring Boot 实现幂等性 ------ 代码实战篇

下面以 "创建订单"接口 为例,展示两种主流方案。

方案1:基于数据库唯一索引(最常用、最可靠)

步骤:
  1. 订单表增加业务唯一 ID 字段(如 biz_order_no
  2. 该字段加唯一索引
  3. 插入前不校验,直接插入,靠 DB 抛异常拦截重复
java 复制代码
// Order.java
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "bizOrderNo"))
public class Order {
    @Id
    private Long id;
    private String bizOrderNo; // 业务订单号,由前端或上游生成
    private String userId;
    private BigDecimal amount;
    // ...
}
java 复制代码
// OrderService.java
@Service
@Transactional
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    public Order createOrder(String bizOrderNo, String userId, BigDecimal amount) {
        Order order = new Order();
        order.setBizOrderNo(bizOrderNo);
        order.setUserId(userId);
        order.setAmount(amount);

        try {
            return orderRepository.save(order); // 若 bizOrderNo 重复,DB 抛出 DuplicateKeyException
        } catch (DataIntegrityViolationException e) {
            // 幂等处理:查询已存在的订单返回
            return orderRepository.findByBizOrderNo(bizOrderNo)
                .orElseThrow(() -> new RuntimeException("订单创建失败且无法找回"));
        }
    }
}

优点 :简单、可靠、天然支持分布式

缺点:依赖 DB 唯一索引,需上游传唯一 ID

方案2:基于 Redis Token 机制(适合表单提交防重)

思路:
  1. 进入下单页面时,后端生成 token 返回给前端
  2. 提交时携带 token
  3. 后端用 Lua 脚本原子校验并删除 token
java 复制代码
// IdempotentController.java
@RestController
public class OrderController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private OrderService orderService;

    // Step 1: 获取防重 Token
    @GetMapping("/token")
    public String getToken() {
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set("idempotent:" + token, "1", Duration.ofMinutes(5));
        return token;
    }

    // Step 2: 创建订单(带幂等 Token)
    @PostMapping("/order")
    public ResponseEntity<Order> createOrder(@RequestHeader("X-Idempotent-Token") String token,
                                             @RequestBody CreateOrderDTO dto) {
        // Lua 脚本保证原子性:存在则删除,不存在则拒绝
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del', KEYS[1]) " +
                        "else return 0 end";

        Boolean consumed = redisTemplate.execute(
            new DefaultRedisScript<>(script, Boolean.class),
            Collections.singletonList("idempotent:" + token),
            "1"
        );

        if (Boolean.TRUE.equals(consumed)) {
            Order order = orderService.createOrder(dto.getBizOrderNo(), dto.getUserId(), dto.getAmount());
            return ResponseEntity.ok(order);
        } else {
            throw new RuntimeException("重复提交,请勿重复操作");
        }
    }
}

优点 :用户体验好,适合前端交互场景

⚠️ 注意:Token 有效期要合理,防止误杀;高并发下需 Lua 保证原子性

四、 专业术语推荐

🎯 高频面试问题:

  1. 什么是接口幂等性?哪些 HTTP 方法是幂等的?
  2. 如何保证支付接口的幂等性?
  3. POST 请求是非幂等的,如何让它变成幂等?
  4. 消息队列重复消费怎么处理?
  5. 数据库唯一索引和 Redis Token 方案各有什么优劣?

💬 推荐使用的专业术语(显得有水准):

  • 业务唯一标识(Business Unique Key)
  • 去重表(Deduplication Table)
  • 状态机幂等(State Machine Idempotency)
  • 乐观锁版本控制(Optimistic Locking with Version)
  • 原子操作(Atomic Operation via Lua Script)
  • 至少一次投递语义(At-Least-Once Delivery Semantics)
  • 副作用(Side Effect)
  • 最终一致性(Eventual Consistency)

🔍 加分回答技巧:

  • 强调:"幂等性是服务端责任,不能依赖前端防重"
  • 区分:"HTTP 语义幂等 ≠ 业务逻辑幂等"
  • 举例:"我们在订单系统中通过 biz_order_no + 唯一索引实现创建幂等,退款通过 refund_no 防重"
  • 提及监控:"我们会记录幂等拦截日志,用于分析重复请求来源"

总结一句话:

"在分布式系统中,网络不可靠是常态,幂等性不是可选项,而是保障数据一致性的底线设计。"

相关推荐
Hero | 柒1 小时前
JAVA反射机制
java·spring·反射
j***63082 小时前
Springboot项目中线程池使用整理
java·spring boot·后端
likuolei2 小时前
Eclipse 创建 Java 接口
java·数据库·eclipse
q***54752 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
a***56062 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
z***75152 小时前
SpringBoot集成MQTT客户端
java·spring boot·后端
q***69772 小时前
java进阶1——JVM
java·开发语言·jvm
码力码力我爱你2 小时前
C++静态变量依赖关系
java·jvm·c++
q***76663 小时前
Java_ElasticSearch(ES)——分布式搜索引擎
java·elasticsearch·搜索引擎