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

一、组件概述

该分布式幂等组件基于 Spring AOP 和 Redis 实现,通过注解声明式配置,提供灵活、高性能的接口幂等性保障。支持快速失败与阻塞等待两种模式,集成自定义策略扩展能力,适用于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 方法定义自定义响应逻辑。
  • 工厂注册:通过 IdemHandlerFactory 或 RespHandlerFactory 注册 Bean。

五、设计模式应用

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

六、总结

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

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

最后

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

相关推荐
武子康几秒前
同为7/8,错误样本却不同:Agent Grader 分歧复算
人工智能·后端·agent
茉莉玫瑰花茶2 分钟前
GO [ 内存与变量逃逸分析 ]
开发语言·后端·golang
星辰云-5 分钟前
基于Spring Boot的旅游景点票务系统的设计与实现(毕设项目,附完整源码)
spring boot·后端·课程设计
Rain的Java大神之路38 分钟前
百万并发直播弹幕系统设计全解:看懂了扇出,你就看懂了高并发的八成
java·后端·面试
山间小僧1 小时前
Agent Memory(三)团队记忆:以 TencentDB Agent Memory 为例
后端·agent
十年Java程序媛1 小时前
JVM 基础与 GC 垃圾回收详解|可达性分析、分代模型、垃圾收集器、STW
java·开发语言·jvm·spring boot·后端·面试
苏三说技术1 小时前
为什么越来越多人用MinerU?
后端
SimonKing1 小时前
别再找笔记了,试试这款轻量级的开源软件:local-notes
后端·程序员·开源
狂师1 小时前
太强了!GitHub上开源了一款AI自动化测试框架,0代码搞定前后端测试!
前端·后端·测试
张张123y1 小时前
后端第十三课:大模型评测(Evals)从 0 到 1——看完面试能讲明白
后端·面试·职场和发展·大模型评测·evals