限流熔断
1. 概述
限流熔断是梵医云系统中用于保护系统稳定性的核心模块,基于 Redis + Redisson 实现分布式限流功能。系统通过 AOP 拦截器的方式,对方法调用进行限流控制,防止系统因过载而崩溃。
限流熔断具有以下特点:
- 分布式支持:基于 Redis 实现,支持分布式环境
- 多种限流策略:支持全局、用户、IP、服务器节点等多种限流策略
- 灵活配置:支持自定义限流时间、次数、时间单位
- 高性能:基于 Redisson 的 RRateLimiter 实现,性能优异
- 易于使用:通过注解方式使用,简单方便
2. 技术架构
2.1 限流架构
请求 -> RateLimiterAspect -> RateLimiterKeyResolver -> RateLimiterRedisDAO -> Redis
-> DefaultRateLimiterKeyResolver
-> UserRateLimiterKeyResolver
-> ClientIpRateLimiterKeyResolver
-> ServerNodeRateLimiterKeyResolver
-> ExpressionRateLimiterKeyResolver核心组件:
- RateLimiter:限流注解,用于标记需要限流的方法
- RateLimiterAspect:限流切面,拦截 @RateLimiter 注解的方法
- RateLimiterKeyResolver:限流 Key 解析器接口
- RateLimiterRedisDAO:限流 Redis 操作类
- RedissonClient:Redisson 客户端,提供 RRateLimiter 功能
2.2 限流算法
系统使用 令牌桶算法 实现限流:
令牌桶 -> 消费者获取令牌 -> 获取成功 -> 执行方法
-> 获取失败 -> 抛出异常特点:
- 固定速率生成令牌
- 令牌桶有最大容量
- 请求到达时获取令牌
- 令牌不足时拒绝请求
2.3 核心组件
| 组件 | 说明 | 位置 |
|---|---|---|
| @RateLimiter | 限流注解 | fanyi-spring-boot-starter-protection |
| RateLimiterAspect | 限流切面 | fanyi-spring-boot-starter-protection |
| RateLimiterKeyResolver | 限流 Key 解析器接口 | fanyi-spring-boot-starter-protection |
| DefaultRateLimiterKeyResolver | 默认(全局)限流 Key 解析器 | fanyi-spring-boot-starter-protection |
| UserRateLimiterKeyResolver | 用户级别限流 Key 解析器 | fanyi-spring-boot-starter-protection |
| ClientIpRateLimiterKeyResolver | IP 级别限流 Key 解析器 | fanyi-spring-boot-starter-protection |
| ServerNodeRateLimiterKeyResolver | 服务器节点级别限流 Key 解析器 | fanyi-spring-boot-starter-protection |
| ExpressionRateLimiterKeyResolver | 表达式限流 Key 解析器 | fanyi-spring-boot-starter-protection |
| RateLimiterRedisDAO | 限流 Redis 操作类 | fanyi-spring-boot-starter-protection |
3. 配置说明
3.1 Maven 依赖
在 pom.xml 中添加依赖:
<dependency>
<groupId>com.fanyi.cloud</groupId>
<artifactId>fanyi-spring-boot-starter-protection</artifactId>
</dependency>3.2 Redis 配置
在 application.yaml 中配置 Redis:
spring:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms3.3 Redisson 配置
在 application.yaml 中配置 Redisson:
spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: null
database: 0
connectionPoolSize: 64
connectionMinimumIdleSize: 10
idleConnectionTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
threads: 16
nettyThreads: 323.4 配置参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| spring.redis.host | String | 是 | - | Redis 服务器地址 |
| spring.redis.port | Integer | 否 | 6379 | Redis 服务器端口 |
| spring.redis.password | String | 否 | - | Redis 密码 |
| spring.redis.database | Integer | 否 | 0 | Redis 数据库索引 |
| spring.redis.timeout | Duration | 否 | 5000ms | Redis 连接超时时间 |
| spring.redis.lettuce.pool.max-active | Integer | 否 | 8 | 连接池最大连接数 |
| spring.redis.lettuce.pool.max-idle | Integer | 否 | 8 | 连接池最大空闲连接数 |
| spring.redis.lettuce.pool.min-idle | Integer | 否 | 0 | 连接池最小空闲连接数 |
4. 使用方式
4.1 @RateLimiter 注解
@RateLimiter 注解用于标记需要限流的方法:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 100
)
public void demoMethod() {
}
}4.2 注解参数说明
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| time | int | 否 | 1 | 限流的时间 |
| timeUnit | TimeUnit | 否 | SECONDS | 时间单位 |
| count | int | 否 | 100 | 限流次数 |
| message | String | 否 | "" | 提示信息 |
| keyResolver | Class<? extends RateLimiterKeyResolver> | 否 | DefaultRateLimiterKeyResolver.class | 使用的 Key 解析器 |
| keyArg | String | 否 | "" | 使用的 Key 参数 |
4.3 全局限流
使用 DefaultRateLimiterKeyResolver 实现全局限流:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 100,
keyResolver = DefaultRateLimiterKeyResolver.class
)
public void demoMethod() {
}
}4.4 用户级别限流
使用 UserRateLimiterKeyResolver 实现用户级别限流:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 10,
keyResolver = UserRateLimiterKeyResolver.class
)
public void demoMethod() {
}
}4.5 IP 级别限流
使用 ClientIpRateLimiterKeyResolver 实现 IP 级别限流:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 10,
keyResolver = ClientIpRateLimiterKeyResolver.class
)
public void demoMethod() {
}
}4.6 服务器节点级别限流
使用 ServerNodeRateLimiterKeyResolver 实现服务器节点级别限流:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.ServerNodeRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 100,
keyResolver = ServerNodeRateLimiterKeyResolver.class
)
public void demoMethod() {
}
}4.7 表达式限流
使用 ExpressionRateLimiterKeyResolver 实现表达式限流:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.ExpressionRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 10,
keyResolver = ExpressionRateLimiterKeyResolver.class,
keyArg = "#userId"
)
public void demoMethod(Long userId) {
}
}5. 业务场景示例
5.1 接口限流
对接口进行限流,防止接口被恶意调用:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class DemoController {
@GetMapping("/api/demo")
@RateLimiter(
time = 1,
timeUnit = TimeUnit.MINUTES,
count = 100,
keyResolver = ClientIpRateLimiterKeyResolver.class,
message = "请求过于频繁,请稍后再试"
)
public String demo() {
return "success";
}
}5.2 发送短信限流
对发送短信接口进行限流,防止短信被恶意发送:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class SmsService {
@RateLimiter(
time = 1,
timeUnit = TimeUnit.DAYS,
count = 10,
keyResolver = UserRateLimiterKeyResolver.class,
message = "今日短信发送次数已达上限"
)
public void sendSms(Long userId, String phone, String content) {
}
}5.3 订单创建限流
对订单创建接口进行限流,防止恶意刷单:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class OrderService {
@RateLimiter(
time = 1,
timeUnit = TimeUnit.MINUTES,
count = 5,
keyResolver = UserRateLimiterKeyResolver.class,
message = "订单创建过于频繁,请稍后再试"
)
public void createOrder(Long userId, CreateOrderReq req) {
}
}5.4 文件上传限流
对文件上传接口进行限流,防止文件上传过快:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class FileService {
@RateLimiter(
time = 1,
timeUnit = TimeUnit.MINUTES,
count = 10,
keyResolver = UserRateLimiterKeyResolver.class,
message = "文件上传过于频繁,请稍后再试"
)
public void uploadFile(Long userId, MultipartFile file) {
}
}5.5 登录限流
对登录接口进行限流,防止暴力破解:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class AuthService {
@RateLimiter(
time = 1,
timeUnit = TimeUnit.MINUTES,
count = 5,
keyResolver = ClientIpRateLimiterKeyResolver.class,
message = "登录尝试过于频繁,请稍后再试"
)
public void login(String username, String password) {
}
}5.6 支付限流
对支付接口进行限流,防止重复支付:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 1,
keyResolver = UserRateLimiterKeyResolver.class,
message = "支付请求过于频繁,请稍后再试"
)
public void pay(Long userId, Long orderId, BigDecimal amount) {
}
}6. 最佳实践
6.1 合理设置限流参数
根据业务场景合理设置限流参数:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 100
)
public void demoMethod() {
}
}建议:
- 对于高频接口,可以设置较大的 count 值
- 对于低频接口,可以设置较小的 count 值
- 对于重要接口,可以设置较短的 time 值
6.2 选择合适的限流策略
根据业务场景选择合适的限流策略:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 10,
keyResolver = UserRateLimiterKeyResolver.class
)
public void demoMethod() {
}
}建议:
- 对于需要区分用户的接口,使用
UserRateLimiterKeyResolver - 对于需要区分 IP 的接口,使用
ClientIpRateLimiterKeyResolver - 对于需要区分服务器的接口,使用
ServerNodeRateLimiterKeyResolver - 对于需要自定义的接口,使用
ExpressionRateLimiterKeyResolver
6.3 自定义限流 Key 解析器
实现自定义的限流 Key 解析器:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import com.fanyi.cloud.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component;
@Component
public class CustomRateLimiterKeyResolver implements RateLimiterKeyResolver {
@Override
public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {
String methodName = joinPoint.getSignature().toString();
String argsStr = StrUtil.join(",", joinPoint.getArgs());
return SecureUtil.md5(methodName + argsStr + "custom");
}
}6.4 友好的错误提示
设置友好的错误提示信息:
import com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class DemoService {
@RateLimiter(
time = 10,
timeUnit = TimeUnit.SECONDS,
count = 100,
message = "请求过于频繁,请稍后再试"
)
public void demoMethod() {
}
}6.5 限流异常处理
处理限流异常:
import com.fanyi.cloud.framework.common.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public String handleServiceException(ServiceException ex) {
log.error("[handleServiceException][业务异常,错误码: {},错误信息: {}]",
ex.getCode(), ex.getMessage());
return ex.getMessage();
}
}6.6 限流监控
记录限流日志:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class RateLimiterLogAspect {
@Before("@annotation(com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter)")
public void beforePointCut(JoinPoint joinPoint) {
log.info("[beforePointCut][方法({}) 被调用]", joinPoint.getSignature().toString());
}
}7. 常见问题
7.1 限流不生效
问题:限流注解添加后不生效
解决方案:
- 检查是否添加了
fanyi-spring-boot-starter-protection依赖 - 检查 Redis 连接是否正常
- 检查方法是否是 public 方法
- 检查方法是否被 Spring 容器管理
7.2 限流参数不生效
问题:限流参数设置后不生效
解决方案:
- 检查限流参数是否正确
- 检查 Redis 中是否已存在旧的限流配置
- 清除 Redis 中的限流配置
7.3 限流 Key 冲突
问题:不同方法的限流 Key 冲突
解决方案:
- 使用不同的限流策略
- 使用表达式限流,指定不同的 keyArg
- 自定义限流 Key 解析器
7.4 限流性能问题
问题:限流影响接口性能
解决方案:
- 检查 Redis 连接池配置
- 检查 Redis 性能
- 优化限流 Key 解析器
7.5 限流异常处理
问题:限流异常没有被正确处理
解决方案:
- 检查全局异常处理器配置
- 检查限流异常类型
- 添加限流异常处理逻辑
8. 监控和调试
8.1 限流日志
记录限流日志:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class RateLimiterMonitorAspect {
@Before("@annotation(com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter)")
public void beforePointCut(JoinPoint joinPoint) {
log.info("[beforePointCut][方法({}) 被调用,参数: {}]",
joinPoint.getSignature().toString(), joinPoint.getArgs());
}
}8.2 限流统计
统计限流次数:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class RateLimiterStatisticsAspect {
private final AtomicLong rateLimiterCount = new AtomicLong(0);
@AfterThrowing(pointcut = "@annotation(com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter)", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
if (ex instanceof ServiceException) {
ServiceException serviceException = (ServiceException) ex;
if (serviceException.getCode().equals(429)) {
long count = rateLimiterCount.incrementAndGet();
log.info("[afterThrowing][限流次数: {},方法: {}]",
count, joinPoint.getSignature().toString());
}
}
}
}8.3 限流告警
配置限流告警:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class RateLimiterAlarmAspect {
private final AtomicLong rateLimiterCount = new AtomicLong(0);
@AfterThrowing(pointcut = "@annotation(com.fanyi.cloud.framework.ratelimiter.core.annotation.RateLimiter)", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
if (ex instanceof ServiceException) {
ServiceException serviceException = (ServiceException) ex;
if (serviceException.getCode().equals(429)) {
long count = rateLimiterCount.incrementAndGet();
if (count % 100 == 0) {
log.warn("[afterThrowing][限流次数达到: {},方法: {}]",
count, joinPoint.getSignature().toString());
}
}
}
}
}9. 注意事项
- Redis 配置:确保 Redis 配置正确,连接正常
- 限流参数:根据业务场景合理设置限流参数
- 限流策略:根据业务场景选择合适的限流策略
- 限流 Key:确保限流 Key 唯一性,避免冲突
- 错误处理:正确处理限流异常
- 日志记录:记录限流日志,便于问题排查
- 性能优化:对于高频接口,优化限流 Key 解析器
- 监控告警:配置限流监控和告警
- 分布式环境:确保 Redis 配置支持分布式环境
- 限流测试:上线前进行限流测试,验证限流效果
