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

相关推荐
烛阴2 小时前
bignumber.js深度解析:驾驭任意精度计算的终极武器
前端·javascript·后端
服务端技术栈2 小时前
电商营销系统中的幂等性设计:从抽奖积分发放谈起
后端
你的人类朋友3 小时前
✍️Node.js CMS框架概述:Directus与Strapi详解
javascript·后端·node.js
面朝大海,春不暖,花不开3 小时前
自定义Spring Boot Starter的全面指南
java·spring boot·后端
钡铼技术ARM工业边缘计算机4 小时前
【成本降40%·性能翻倍】RK3588边缘控制器在安防联动系统的升级路径
后端
CryptoPP4 小时前
使用WebSocket实时获取印度股票数据源(无调用次数限制)实战
后端·python·websocket·网络协议·区块链
白宇横流学长4 小时前
基于SpringBoot实现的大创管理系统设计与实现【源码+文档】
java·spring boot·后端
草捏子5 小时前
状态机设计:比if-else优雅100倍的设计
后端
考虑考虑6 小时前
Springboot3.5.x结构化日志新属性
spring boot·后端·spring
涡能增压发动积7 小时前
一起来学 Langgraph [第三节]
后端