分布式接口限流与防重复提交实现方案
一、背景
在分布式系统架构下,接口的稳定性与安全性是系统设计的核心挑战之一。随着业务规模的扩大和高并发场景的增多,接口面临的流量洪峰、恶意刷单、重复提交等问题日益突出:
- 流量过载风险:突发流量(如活动促销、热点事件)可能导致核心接口超出系统承载能力,引发服务雪崩;
- 恶意请求攻击:未授权用户通过脚本高频调用接口,消耗服务器资源(如CPU、数据库连接),影响正常业务;
- 重复提交隐患:用户因网络延迟重复点击按钮,或前端未做防重校验时,可能导致业务逻辑重复执行(如重复下单、重复扣款)。
传统单机限流方案(如Guava RateLimiter)仅能控制单个应用实例的流量,无法满足分布式系统多实例间的协同限流需求。而基于Redis的分布式限流方案,凭借其全局状态共享、高并发支持和原子性操作的特性,成为解决分布式限流问题的首选。
Redisson作为Redis的Java客户端增强工具,内置了RRateLimiter
(分布式限流器)组件,支持基于时间窗口的令牌桶算法,能够轻松实现跨实例的分布式限流。本文将结合Spring Boot框架,通过自定义注解与AOP切面,演示如何基于Redisson快速实现一套低侵入、可配置、分布式的接口限流与防重复提交方案。
二、依赖引入
在Spring Boot项目中,通过redisson-spring-boot-starter
快速集成Redisson,只需添加以下Maven依赖(版本号根据实际情况调整):
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>${redisson.version}</version>
</dependency>
三、限流注解定义
为了实现无侵入式的限流控制,我们定义一个自定义注解@RateLimit
,用于标记需要限流的方法。该注解支持配置时间窗口(单位:秒)和限流键前缀,灵活适配不同业务场景的限流需求。
import java.lang.annotation.*;/*** 分布式限流注解(基于 Redisson 实现)* 用于标记需要限制调用频率的方法,防止接口被高频调用或重复提交*/
@Target(ElementType.METHOD) // 作用于方法级别
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取
@Documented // 文档可见
@Inherited // 支持子类继承
public @interface RateLimit {/*** 限流时间窗口(单位:秒)* 表示在多长时间内允许的最大请求次数(默认60秒)*/int seconds() default 60;/*** 限流键前缀* 用于生成Redis中限流规则的唯一键,建议根据业务场景命名(如"order:submit")*/String key_pre();
}
四、分布式限流切面实现
通过Spring AOP的环绕通知(@Around
),拦截所有标记了@RateLimit
的方法,在方法执行前进行限流校验。核心逻辑包括:
- 生成唯一限流键:结合限流键前缀与请求参数的SHA256摘要,确保不同参数的请求独立计数;
- 初始化限流器:通过Redisson的
RRateLimiter
创建分布式限流器,设置时间窗口内的最大请求次数; - 执行限流校验:若当前请求超出限流阈值,则拦截请求并返回提示;否则放行请求,执行原方法。
- 更换限流规则:标记为3处的代码可与标记为4处的代码置换位置,实现两种限流逻辑
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import zyy.oa.framework.common.annotations.RateLimit;
import zyy.oa.framework.common.utils.Result;
import cn.hutool.core.lang.Assert;import java.lang.reflect.Method;
import java.time.Duration;@Aspect
@Component
@AllArgsConstructor
public class CommonAspect {private final RedissonClient redissonClient; // Redisson客户端注入/*** 环绕通知:拦截所有标记了@RateLimit的方法,执行限流校验* @param pjp 切入点对象* @param rateLimit 限流注解参数* @return 原方法执行结果或限流提示* @throws Throwable 异常*/@Around("@annotation(rateLimit)")public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {// 1. 解析方法参数,生成唯一限流键Method method = ((MethodSignature) pjp.getSignature()).getMethod();Object[] args = pjp.getArgs(); // 获取方法入参String paramsJson = JSONObject.toJSONString(args); // 参数转JSON字符串String requestKey = DigestUtil.sha256Hex(paramsJson); // 参数SHA256摘要(防重复关键)String limitKey = "rate_limit:" + rateLimit.key_pre() + ":" + requestKey; // 完整限流键// 2. 初始化Redisson限流器(仅首次请求时设置)RRateLimiter rateLimiter = redissonClient.getRateLimiter(limitKey);boolean isFirstRequest = rateLimiter.trySetRate(RateType.OVERALL, // 限流类型:全局(所有节点共享)1, // 时间窗口内最大请求数(此处设为1次/窗口)Duration.ofSeconds(rateLimit.seconds()) // 时间窗口长度);// 3.确保键过期时间与窗口一致rateLimiter.expire(Duration.ofSeconds(rateLimit.seconds())); // 4. 执行限流校验:尝试获取令牌(无令牌则拦截),非首次请求触发限流(首次请求可能因初始化延迟未生效,不拦截)// 可根据需要与3处代码位置置换实现不计入限流请求Assert.isTrue(rateLimiter.tryAcquire()&&firstTime, "限定时间内重复请求,已忽略");// 5. 限流通过,执行原方法return pjp.proceed();}
}
五、使用示例
在需要限流的业务方法上添加@RateLimit
注解,即可快速启用分布式限流功能。以下是一个典型的接口限流场景:
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import zyy.oa.framework.common.utils.Result;
import java.util.Map;@RestController
public class FlowController {private final FlowDefinitionService flowDefinitionService;// 示例:限制"自动发起流程"接口的调用频率@Operation(summary = "自动发起流程")@PostMapping("/autoStartFlow")@RateLimit(key_pre = "flow:autoStart", // 限流键前缀(业务相关,可以根据接口自定义)seconds = 60 // 时间窗口60秒(1分钟内仅允许1次请求))public Result autoStartFlow(@Parameter(description = "流程分类") @RequestParam String category,@Parameter(description = "流程名称") @RequestParam(required = false) String name,@Parameter(description = "变量集合") @RequestBody Map<String, Object> variables) {return flowDefinitionService.autoStartFlow(category, name, variables);}
}
六、方案特性与应用场景
方案特性
特性 | 说明 |
---|---|
分布式支持 | 基于Redis存储限流状态,天然支持多实例集群环境,各节点限流规则同步 |
低侵入性 | 通过注解+AOP实现,业务代码无需修改,仅需标记需要限流的方法 |
参数敏感性 | 结合请求参数生成SHA256摘要,不同参数的请求视为独立操作,避免误限 |
灵活配置 | 支持自定义时间窗口(seconds )和键前缀(key_pre ),适配不同业务场景 |
高并发友好 | Redisson的RRateLimiter 基于Lua脚本实现原子操作,保证高并发下的准确性 |
应用场景
- 核心接口防刷:如支付接口、短信发送接口,限制单位时间内的调用次数;
- 防重复提交:表单提交、订单确认等操作,防止用户重复点击导致业务重复执行;
- 资源消耗保护:批量任务接口、数据导出接口,限制高频调用对服务器资源的消耗;
- 业务风控:配合用户行为分析,对异常高频请求(如短时间内大量不同参数调用)进行拦截。
总结
本文基于Redisson实现了分布式接口限流与防重复提交方案,通过自定义注解和AOP切面,以低侵入的方式解决了分布式系统中的流量控制问题。该方案兼顾了灵活性、准确性和可维护性,适用于大多数需要限制接口调用频率的业务场景。实际使用中,可根据业务需求调整限流时间窗口、最大请求数等参数,或扩展注解功能(如支持动态限流策略),进一步适配复杂业务场景。