让代码自带「防重」Buff: 手写分布式幂等组件

一、组件概述

该分布式幂等组件基于 Spring AOPRedis 实现,通过注解声明式配置,提供灵活、高性能的接口幂等性保障。支持快速失败与阻塞等待两种模式,集成自定义策略扩展能力,适用于springboot项目分布式场景下的重复请求拦截与处理。


代码已上传github,欢迎star: 传送门

二、核心功能点

  1. 声明式注解支持
    通过 @Idempotent 注解标记需幂等控制的方法,支持参数动态配置。
  2. 多种策略组合
    • 幂等策略:默认基于 Redis 缓存,支持自定义处理器(如数据库持久化)。
    • 响应策略 :支持返回空值(NULL)、抛出异常(EXCEPTION)、返回历史结果(PREVIOUS)。
  3. 并发控制
    基于 Redisson 分布式锁实现,支持快速失败(failFast=true)或阻塞等待锁(failFast=false)。
  4. 动态参数识别
    通过 columns 参数指定幂等键生成规则,支持 EL 表达式动态提取参数。
  5. 自动配置与扩展
    通过 Spring Boot starter 自动装配,支持自定义处理器工厂扩展,无缝集成现有系统。

三、代码逻辑流程

1. 请求拦截与上下文构建

  • AOP 切面拦截 :通过 IdempotentAspect 拦截被 @Idempotent 注解标记的方法。
  • 生成唯一标识 :根据 uid类名+方法名 构建唯一键,结合参数生成 Redis Key。
  • 上下文初始化 :收集参数、策略配置、处理器实例,封装为 IdempotentContext

核心代码:

java 复制代码
    @Around("@annotation(anno)")
    public final Object proceed(ProceedingJoinPoint joinPoint, Idempotent anno) throws Throwable {
        // 所有的参数值的数组
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 幂等增强 委托给IdempotentExecutor
        Object retObj = executor.execute(new IdempotentCallback<Object>() {
            @Override
            public IdempotentContext<Object> initContext() throws Throwable {
                return new IdempotentContext<>()
                        .setUid(buildUid(joinPoint, signature, anno))
                        .setDuration(anno.duration())
                        .setArgMap(buildArgMap(joinPoint, signature))
                        .setIdemParam(buildIdemParam(joinPoint, anno))
                        .setFailFast(anno.failFast())
                        .setIdemStrategy(anno.custom().idemStrategy())
                        .setIdemHandler(IdemHandlerUtils.getIdemHandler(joinPoint, anno.custom()))
                        .setRespStrategy(anno.custom().respStrategy())
                        .setRespHandler(IdemHandlerUtils.getRespHandler(joinPoint, anno.custom()));
            }

            @Override
            public Object execute() throws Throwable {
                return joinPoint.proceed();
            }
        });
        return JSON.parseObject(JSON.toJSONString(retObj), (Class<?>) signature.getReturnType());
    }

2. 幂等校验与执行

  • 分布式锁控制 :通过 IdempotentExecutor 获取 Redisson 锁,确保并发安全。
    • 快速失败模式:尝试获取锁,失败则直接触发响应策略。
    • 阻塞模式:等待锁释放后执行逻辑。
  • 持久化校验 :调用 PersistProcessor 检查 Redis 中是否存在历史记录,避免重复提交。
  • 业务逻辑执行:若校验通过,执行业务代码并将结果持久化到 Redis。

3. 结果处理

  • 自定义响应:根据策略返回预定义结果(如异常、空值或历史数据)。
  • 自动清理:通过 Redis 的 TTL 机制自动清理过期记录。

核心代码:

java 复制代码
   public final <R> R execute(IdempotentCallback<R> callback) throws Throwable {

        IdempotentContext<R> context = callback.initContext();
        String uid = context.getUid();
        if (StringUtils.isBlank(uid)) {
            throw new Exception("幂等组件: uid不能为空");
        }
        List<Object> param = context.getIdemParam();
        if (CollectionUtils.isEmpty(param)) {
            throw new Exception("幂等组件: idemParam不能为空");
        }
        RLock lock = client.getLock(buildLock(uid, param));
        return context.isFailFast() ? doFast(callback, context, lock) : doLazy(callback, context, lock);
    }

    private <R> R doLazy(IdempotentCallback<R> callback, IdempotentContext<R> context, RLock lock) throws Throwable {
        lock.lock();
        try {
            IdempotentResult<R> result = context.getIdemHandler().handle(context);
            if (result.isFlag()) {
                //执行
                R r = callback.execute();
                //缓存结果,自定义策略不缓存
                processor.persist(context.setResult(r));
                return r;
            }
            return context.setResult(result.getResult()).getRespHandler().handle(context);
        } finally {
            lock.unlock();
        }
    }

    private <R> R doFast(IdempotentCallback<R> callback, IdempotentContext<R> context, RLock lock) throws Throwable {
        if (lock.tryLock()) {
            try {
                //获取到锁,执行逻辑
                IdempotentResult<R> result = context.getIdemHandler().handle(context);
                if (result.isFlag()) {
                    //执行
                    R r = callback.execute();
                    //缓存结果,自定义策略不缓存
                    processor.persist(context.setResult(r));
                    return r;
                }
            } finally {
                lock.unlock();
            }
        }
        return context.getRespHandler().handle(context);
    }

四、使用方式

1. 添加依赖(pom.xml)

xml 复制代码
<dependency>
    <groupId>com.shemuel</groupId>
    <artifactId>idempotent-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

2. 配置文件

yaml 复制代码
idempotent:
  enable: true
  env: prod
  redis:
    host: 127.0.0.1
    port: 6379
    password: redis-pass

3. 添加注解

java 复制代码
@Idempotent(
    uid = "order.create", 
    columns = {"#userId", "#orderId"}, // 动态提取参数
    failFast = true,
    duration = 10,
    custom = @Strategy(
        idemStrategy = IdemStrategy.DEFAULT,
        respStrategy = RespStrategy.EXCEPTION
    )
)
public Order createOrder(Long userId, String orderId) {
    // 业务逻辑
}

4. 自定义扩展(按需)

  • 实现 IdemHandler :覆盖 doHandle 方法定义自定义幂等逻辑。
  • 实现 RespHandler:覆盖 doHandle 方法定义自定义响应逻辑。
  • 工厂注册:通过 IdemHandlerFactoryRespHandlerFactory 注册 Bean。

五、设计模式应用

  1. 模板方法模式
    IdempotentExecutor 封装执行流程,子流程(如锁获取、持久化)通过回调接口 IdempotentCallback 实现。
  2. 策略模式
    通过 IdemStrategyRespStrategy 定义多套处理策略,支持运行时动态切换。
  3. 工厂模式
    IdemHandlerFactoryRespHandlerFactory 集中管理处理器实例,解耦创建与使用逻辑。
  4. 代理模式
    Spring AOP 动态代理拦截方法调用,实现非侵入式幂等控制。
  5. 单例模式
    Spring Bean 默认单例,确保处理器和配置类的全局唯一性。

六、总结

该组件通过 注解驱动 + 分布式锁 + 策略扩展 的设计,实现了轻量级、高可用的幂等控制。开发者无需修改业务代码即可集成,同时支持灵活扩展,适用于电商、金融等高并发场景,有效保障系统数据一致性。

代码已上传github, 欢迎star; github.com/ShemuelDeng...

最后

欢迎关注gzh:加瓦点灯, 每天推送干货知识!

相关推荐
掘金者阿豪13 分钟前
数据库迁移工具选了半年,最后发现决策的起点错了
后端
卷无止境17 分钟前
FastAPI查询参数模型:把散落的参数收拢成一个整齐的盒子
后端·python
gis开发之家1 小时前
Spring Boot 4 深度解析——参数接收大全:@RequestParam、@PathVariable、@RequestBody
java·spring boot·后端·spring
NutShell Wang1 小时前
Rust 1.97 实战迁移:v0 符号重整、Cargo 警告治理与位运算新 API
人工智能·后端·性能优化·rust·vibe coding
阑梦清川1 小时前
零成本把笔记转成双人播客:WorkBuddy + 腾讯云 TTS 完整教程
后端
赫媒派1 小时前
Go 1.27 来了:泛型方法补齐,JSON 提速不踩坑
后端·go·敏捷开发
不ok哥男人1 小时前
C# Roslyn 编译器平台实战:源生成器、分析器与代码修补
后端
长栎1 小时前
99% 的人把桥接模式当策略模式用——抽象与实现分离,你做的不是同一件事
后端
长栎1 小时前
你激活了 sharp-skills 一个模块,但你的项目从来不是单点活儿
后端
Lcos1 小时前
Kubernetes Toleration 六种写法详解:从精确匹配到全部放行
后端