分布式接口幂等性的演进和最佳实践,含springBoot 实现(Java版本)

一、背景:为什么需要幂等性

在微服务、分布式架构下,网络不可靠、请求重试机制(如前端超时重发、客户端重发、网关重试、消息消费失败重试等)会带来重复请求,如果接口没有幂等性,可能导致:

  • 重复扣费
  • 重复订单
  • 重复数据写入
  • 资源状态异常

因此接口的幂等性是保障系统一致性、稳定性的关键。


二、幂等性的演进历程

阶段 实现方式 适用场景 特点
1. 前端防重复提交 禁用按钮、Token机制 简单场景(如表单) 限制小,易绕过
2. 数据库唯一约束 唯一索引字段控制插入 插入类接口 利用数据库能力
3. 幂等标识(唯一业务ID) 唯一业务号控制幂等 订单类接口 明确控制粒度
4. 分布式锁 请求上锁 任意接口 控制请求串行,牺牲吞吐
5. 状态机机制 状态+版本控制 状态转换操作 精细控制副作用
6. Redis 幂等性控制 幂等Key+过期时间 高并发下 快速、安全,结合令牌机制

三、最佳实践总结

🧠 原则

  • 幂等性以"结果相同"为基准,副作用不可重复。
  • 控制粒度应适当:写操作必须幂等,读操作天然幂等。
  • 幂等 Token 最好由客户端生成或服务端下发后强制绑定。

✅ 最佳实践建议

  • 业务唯一标识驱动幂等性,如订单号、支付流水号。
  • 幂等性应放在业务代码逻辑前端处理,即"第一次判断是否重复提交"。
  • 采用 Redis + 原子操作(如SETNX)+ Token + TTL 实现防重。
  • 接口幂等性建议作为注解能力进行抽象封装,统一使用。

四、Spring Boot 实现示例(完善版)

下面我们以 Token机制 + Redis原子写入实现接口幂等性,构建完整示例。

1️⃣ 添加依赖

xml 复制代码
<!-- Redis starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2️⃣ Redis配置类(Lettuce连接池)

java 复制代码
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

3️⃣ 幂等Token生成接口(服务端)

java 复制代码
@RestController
@RequestMapping("/token")
public class TokenController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/generate")
    public String generateToken() {
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set("idem-token:" + token, "valid", 5, TimeUnit.MINUTES);
        return token;
    }
}

4️⃣ 幂等校验注解

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    String key() default "";  // 可自定义Key策略
}

5️⃣ 幂等校验切面

java 复制代码
@Aspect
@Component
public class IdempotentAspect {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Around("@annotation(idempotent)")
    public Object checkIdempotency(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader("Idempotency-Token");

        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("幂等Token不能为空");
        }

        String redisKey = "idem-token:" + token;

        Boolean result = redisTemplate.delete(redisKey);  // 核心逻辑:删除即消费
        if (Boolean.FALSE.equals(result)) {
            throw new RuntimeException("重复请求,请勿重复提交");
        }

        return joinPoint.proceed();
    }
}

6️⃣ 接口使用示例

java 复制代码
@RestController
@RequestMapping("/order")
public class OrderController {

    @PostMapping("/create")
    @Idempotent
    public String createOrder(@RequestBody OrderDTO orderDTO) {
        // 模拟业务逻辑
        return "订单创建成功,订单号:" + UUID.randomUUID().toString();
    }
}

7️⃣ Postman 调用流程:

  1. 调用 GET /token/generate 获取 token

  2. 调用 POST /order/create,在请求头加上:

    复制代码
    Idempotency-Token: 你刚刚拿到的token

多次调用同一个 token,只会成功一次,后续将抛出"重复请求"异常。


五、可扩展建议

功能 实现方式
支持注解中的自定义 key 逻辑 SpEL 表达式,读取参数字段
幂等记录持久化 Redis + 数据库双写
异常重试队列支持 Kafka / RocketMQ 消息幂等处理
Redisson替换RedisTemplate 使用 RLock.tryLock() 可实现锁粒度的幂等控制

六、架构图(逻辑)

复制代码
客户端
   |
[请求附带Token]
   |
Spring Boot 接口(注解拦截)
   |
切面逻辑校验 Token
   |
Redis SETNX 删除 key -> 是第一次? -> 继续业务
   |
业务逻辑执行(订单创建、支付、提交...)

后续实例回陆续添加

参数幂等、结果缓存、幂等日志记录

相关推荐
YuTaoShao1 小时前
【LeetCode 热题 100】139. 单词拆分——(解法一)记忆化搜索
java·算法·leetcode·职场和发展
Best_Liu~2 小时前
策略模式 vs 适配器模式
java·spring boot·适配器模式·策略模式
小陈永不服输2 小时前
Windows下RabbitMQ完整安装指南
windows·分布式·rabbitmq
direction__2 小时前
Java Main无法初始化主类的原因与解决方法(VsCode工具)
java·vscode
帧栈2 小时前
开发避坑指南(29):微信昵称特殊字符存储异常修复方案
java·mysql
每天的每一天2 小时前
面试可能问到的问题思考-Redis
java
青云交3 小时前
Java 大视界 -- Java 大数据在智能安防人脸识别系统中的活体检测与防伪技术应用
java·大数据·生成对抗网络·人脸识别·智能安防·防伪技术·活体测试
学习至死qaq3 小时前
信创产品TongLinkQ安装及springboot2整合使用
java·东方通·tonglinkq
我崽不熬夜3 小时前
Java中基本的输入输出(I/O)操作:你知道如何处理文件吗?
java·后端·java ee
我崽不熬夜3 小时前
Java的异常处理机制:如何优雅地捕获和抛出异常?
java·后端·java ee