一、背景
在分布式系统架构下,接口的稳定性与安全性是系统设计的核心挑战之一。随着业务规模的扩大和高并发场景的增多,接口面临的流量洪峰 、恶意刷单 、重复提交等问题日益突出:
- 流量过载风险:突发流量(如活动促销、热点事件)可能导致核心接口超出系统承载能力,引发服务雪崩;
- 恶意请求攻击:未授权用户通过脚本高频调用接口,消耗服务器资源(如CPU、数据库连接),影响正常业务;
- 重复提交隐患:用户因网络延迟重复点击按钮,或前端未做防重校验时,可能导致业务逻辑重复执行(如重复下单、重复扣款)。
传统单机限流方案(如Guava RateLimiter)仅能控制单个应用实例的流量,无法满足分布式系统多实例间的协同限流需求。而基于Redis的分布式限流方案,凭借其全局状态共享 、高并发支持 和原子性操作的特性,成为解决分布式限流问题的首选。
Redisson作为Redis的Java客户端增强工具,内置了RRateLimiter
(分布式限流器)组件,支持基于时间窗口的令牌桶算法,能够轻松实现跨实例的分布式限流。本文将结合Spring Boot框架,通过自定义注解与AOP切面,演示如何基于Redisson快速实现一套低侵入、可配置、分布式的接口限流与防重复提交方案。
二、依赖引入
在Spring Boot项目中,通过redisson-spring-boot-starter
快速集成Redisson,只需添加以下Maven依赖(版本号根据实际情况调整):
XML
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
三、限流注解定义
为了实现无侵入式的限流控制,我们定义一个自定义注解@RateLimit
,用于标记需要限流的方法。该注解支持配置时间窗口 (单位:秒)和限流键前缀,灵活适配不同业务场景的限流需求。
java
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处的代码置换位置,实现两种限流逻辑
java
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
注解,即可快速启用分布式限流功能。以下是一个典型的接口限流场景:
java
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切面,以低侵入的方式解决了分布式系统中的流量控制问题。该方案兼顾了灵活性、准确性和可维护性,适用于大多数需要限制接口调用频率的业务场景。实际使用中,可根据业务需求调整限流时间窗口、最大请求数等参数,或扩展注解功能(如支持动态限流策略),进一步适配复杂业务场景。