对Java接口进行幂等性控制的三种方法

 更新时间:2025年06月24日 10:08:14   作者:天天摸鱼的java工程师  
在做分布式系统、支付系统、电商秒杀等实际项目中,我们经常会遇到接口被重复调用的问题,像用户支付时多次点击“支付”按钮,消息队列消费失败后自动重试等,这些行为如果没有控制好幂等性,会产生重复数据,所以本文给大家介绍了对Java接口进行幂等性控制的三种方法

前言

在做分布式系统、支付系统、电商秒杀等实际项目中,我们经常会遇到接口被重复调用的问题。比如:

  • 用户支付时多次点击“支付”按钮;
  • 网络重试机制导致接口多次请求;
  • 消息队列消费失败后自动重试。

这些行为如果没有控制好幂等性,轻则产生重复数据,重则产生资金损失、库存混乱等严重问题。

今天我们深入聊聊:如何在 Java 中实现接口的幂等性控制?

什么是幂等性?

**幂等性(Idempotent)**是指一个接口被调用多次,结果与调用一次的效果相同

  • GET /order/123 —— 天然幂等。
  • POST /order/create —— 非幂等,需要控制。

幂等性控制的三大核心手段

在我的项目实战中,主要使用以下三种方式实现接口幂等控制:

  • 数据库唯一索引控制
  • Redis 防重复提交
  • 幂等 Token 机制

下面我们逐个拆解原理与代码实现。

1、数据库唯一索引控制(经典可靠)

原理

利用数据库的唯一约束,防止插入重复数据。

适用场景

  • 创建订单、支付单等“只允许一次成功”的业务操作。
  • 数据库操作为最终落地。

实现

假设有个订单表 order,我们希望一个 clientOrderNo(客户端订单号)只能插入一次。

ALTER TABLE t_order ADD UNIQUE KEY uk_client_order_no (client_order_no);

Java 代码示例

public void createOrder(String clientOrderNo, OrderDTO dto) {
    try {
        Order order = new Order();
        order.setClientOrderNo(clientOrderNo);
        order.setAmount(dto.getAmount());
        order.setUserId(dto.getUserId());
        orderRepository.insert(order); // 会触发唯一索引约束
    } catch (DuplicateKeyException e) {
        log.warn("订单已存在,幂等处理: {}", clientOrderNo);
        // 查询已有订单并返回,保持幂等
        Order existing = orderRepository.findByClientOrderNo(clientOrderNo);
        return existing;
    }
}

总结

优点:

  • 简单可靠,数据库层强力保证。

缺点:

  • 粒度粗,如果涉及复杂流程(如多表插入)需结合事务控制。

2、Redis 防重复提交(轻量方案)

原理

利用 Redis 的原子性,通过 SETNX 命令设置唯一键,控制某个请求只处理一次。

适用场景

  • 表单防重复提交。
  • 接口短时间内禁止重复请求。

Java 实现

public boolean tryAcquireRequest(String key, long expireSeconds) {
    // 原子设置键 + 过期时间,表示该请求已处理
    return Boolean.TRUE.equals(
        redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofSeconds(expireSeconds))
    );
}

Controller 示例

@PostMapping("/api/pay")
public ResponseEntity<?> doPay(@RequestBody PayRequest request) {
    String redisKey = "pay:" + request.getUserId() + ":" + request.getOrderId();

    if (!idempotentService.tryAcquireRequest(redisKey, 30)) {
        return ResponseEntity.status(HttpStatus.CONFLICT).body("重复请求,请稍后再试");
    }

    // 执行支付逻辑
    paymentService.pay(request.getOrderId());

    return ResponseEntity.ok("支付成功");
}

总结

优点:

  • 高性能,适合高并发。
  • 不依赖数据库操作。

缺点:

  • Redis异常时无法保证幂等。
  • 需手动构造唯一 key。

3、幂等 Token 机制(前后端协作)

原理

前端首次请求时从服务端获取一个 token,提交表单时附带该 token,服务端验证 token 是否已被使用。

适用场景

  • 表单提交、下单等需要用户主动确认的操作。
  • 控制用户操作行为。

实现步骤

1. 生成幂等 token(后端)

@GetMapping("/token")
public String generateToken() {
    String token = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set("token:" + token, "1", Duration.ofMinutes(5));
    return token;
}

2. 提交接口验证 token

@PostMapping("/submit")
public ResponseEntity<?> submitForm(@RequestParam String token, @RequestBody FormDTO form) {
    String redisKey = "token:" + token;

    // Redis 的 delete 操作返回 1 表示成功删除(即 token 存在)
    Boolean success = redisTemplate.delete(redisKey);

    if (Boolean.FALSE.equals(success)) {
        return ResponseEntity.status(HttpStatus.CONFLICT).body("请勿重复提交");
    }

    // 执行业务逻辑
    formService.process(form);

    return ResponseEntity.ok("提交成功");
}

总结

优点:

  • 精准控制用户行为。
  • 非常适合前后端协作系统。

缺点:

  • 实现略复杂,强依赖 Redis。
  • 前端需配合使用。

实战建议

方法场景适用幂等级别复杂度推荐备注
数据库唯一索引订单、支付等数据落库推荐首选
Redis 防重复提交高并发接口、表单提交配合使用
Token 机制用户行为防重复前后端配合使用

总结

幂等性控制不是一个「万能解」,而是需要根据实际业务场景选择合适的方案。作为有多年经验的后端工程师,我通常会:

  • 数据插入场景首选数据库唯一索引;
  • 接口限流或重复提交保护使用 Redis;
  • 用户行为防重复引入 Token 机制。

幂等性虽“小”,但不控制好,问题很“大”。希望本文对你理解幂等控制的原理和实现有所帮助。

以上就是对Java接口进行幂等性控制的三种方法的详细内容,更多关于Java接口幂等性控制的资料请关注脚本之家其它相关文章!

相关文章

  • Java生成二维码的实现方式汇总

    Java生成二维码的实现方式汇总

    本文将基于Spring Boot介绍两种生成二维码的实现方式,一种是基于Google开发工具包,另一种是基于Hutool来实现,下面我们将基于Spring Boot,并采用两种方式实现二维码的生成,对于每一种方式还提供两种类型的二维码返回形式,需要的朋友可以参考下
    2023-09-09
  • IntelliJ IDEA中打开拼写检查与忽略提示曲线的方法

    IntelliJ IDEA中打开拼写检查与忽略提示曲线的方法

    今天小编就为大家分享一篇关于IntelliJ IDEA中打开拼写检查与忽略提示曲线的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10
  • 分析 Java Stream 的 peek使用实践与副作用处理方案

    分析 Java Stream 的 peek使用实践与副作用处理方案

    Stream API的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限,本文给大家介绍Java Stream的peek使用实践与副作用,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • springboot整合redis集群过程解析

    springboot整合redis集群过程解析

    这篇文章主要介绍了springboot整合redis集群过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • 学习Java之异常到底该如何捕获和处理

    学习Java之异常到底该如何捕获和处理

    我们知道,Java的异常处理是通过5个关键字来实现的,即try、catch、throw、throws和finally,try catch语句用于捕获并处理异常,但具体该怎么捕获异常,怎么抛出异常,什么时候抛,什么时候捕,感兴趣的小伙伴跟着小编一起来看看吧
    2023-08-08
  • Java并发容器之ConcurrentLinkedQueue详解

    Java并发容器之ConcurrentLinkedQueue详解

    这篇文章主要介绍了Java并发容器之ConcurrentLinkedQueue详解,加锁队列的实现较为简单,这里就略过,我们来重点来解读一下非阻塞队列,
    从点到面, 下面我们来看下非阻塞队列经典实现类ConcurrentLinkedQueue,需要的朋友可以参考下
    2023-12-12
  • SpringBoot详解整合Spring Boot Admin实现监控功能

    SpringBoot详解整合Spring Boot Admin实现监控功能

    这篇文章主要介绍了SpringBoot整合Spring Boot Admin实现服务监控,内容包括Server端服务开发,Client端服务开发其中Spring Boot Admin还可以对其监控的服务提供告警功能,如服务宕机时,可以及时以邮件方式通知运维人员,感兴趣的朋友跟随小编一起看看吧
    2022-07-07
  • java 对称加密算法实现详解

    java 对称加密算法实现详解

    这篇文章主要介绍了java 对称加密算法实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • 15道非常经典的Java面试题 附详细答案

    15道非常经典的Java面试题 附详细答案

    这篇文章主要为大家推荐了15道非常经典的Java面试题,附详细答案,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • Java Spring读取和存储详细操作

    Java Spring读取和存储详细操作

    这篇文章主要介绍了Spring读取和存储详细操作,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-08-08

最新评论