让代码自带「防重」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:加瓦点灯, 每天推送干货知识!

相关推荐
Asthenia04121 分钟前
MySQL:系统表/货币数据类型/MyIsam和Innodb/insert性能优化/邻键锁的退化/MySQL的参数/CPU问题排查
后端
计算机-秋大田3 分钟前
基于Spring Boot的产业园区智慧公寓管理系统的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
若汝棋茗9 分钟前
在ASP.NET Core中使用NLog:配置与性能优化指南
后端·性能优化·asp.net·nlog
图南随笔28 分钟前
Spring Boot(十六):拦截器Interceptor
java·spring boot·后端
一只小松许️34 分钟前
Rust函数、条件语句、循环
开发语言·后端·rust
Yeauty40 分钟前
三分钟掌握音频提取 | 在 Rust 中优雅地处理视频音频
后端·rust·ffmpeg·音视频·音频·视频
uhakadotcom1 小时前
阿里云Hologres:实时数据仓库,让数据洞察触手可及
后端·面试
灏瀚星空1 小时前
运行时智控:PanLang 开发者指南(一)运行时系统核心模块实现——PanLang 原型全栈设计方案与实验性探索5
开发语言·人工智能·经验分享·后端·重构·模板方法模式·源代码管理
天草二十六_简村人1 小时前
微信小程序的业务域名配置(通过ingress网关的注解)
后端·微服务·微信小程序·小程序·k8s·kong
uhakadotcom1 小时前
一文搞懂CDH SQL:大数据处理的利器
后端·面试·github