Skip to content

限流熔断

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
ClientIpRateLimiterKeyResolverIP 级别限流 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 中添加依赖:

xml
<dependency>
    <groupId>com.fanyi.cloud</groupId>
    <artifactId>fanyi-spring-boot-starter-protection</artifactId>
</dependency>

3.2 Redis 配置

application.yaml 中配置 Redis:

yaml
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: -1ms

3.3 Redisson 配置

application.yaml 中配置 Redisson:

yaml
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: 32

3.4 配置参数说明

参数类型必填默认值说明
spring.redis.hostString-Redis 服务器地址
spring.redis.portInteger6379Redis 服务器端口
spring.redis.passwordString-Redis 密码
spring.redis.databaseInteger0Redis 数据库索引
spring.redis.timeoutDuration5000msRedis 连接超时时间
spring.redis.lettuce.pool.max-activeInteger8连接池最大连接数
spring.redis.lettuce.pool.max-idleInteger8连接池最大空闲连接数
spring.redis.lettuce.pool.min-idleInteger0连接池最小空闲连接数

4. 使用方式

4.1 @RateLimiter 注解

@RateLimiter 注解用于标记需要限流的方法:

java
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 注解参数说明

参数类型必填默认值说明
timeint1限流的时间
timeUnitTimeUnitSECONDS时间单位
countint100限流次数
messageString""提示信息
keyResolverClass<? extends RateLimiterKeyResolver>DefaultRateLimiterKeyResolver.class使用的 Key 解析器
keyArgString""使用的 Key 参数

4.3 全局限流

使用 DefaultRateLimiterKeyResolver 实现全局限流:

java
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 实现用户级别限流:

java
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 级别限流:

java
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 实现服务器节点级别限流:

java
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 实现表达式限流:

java
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 接口限流

对接口进行限流,防止接口被恶意调用:

java
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 发送短信限流

对发送短信接口进行限流,防止短信被恶意发送:

java
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 订单创建限流

对订单创建接口进行限流,防止恶意刷单:

java
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 文件上传限流

对文件上传接口进行限流,防止文件上传过快:

java
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 登录限流

对登录接口进行限流,防止暴力破解:

java
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 支付限流

对支付接口进行限流,防止重复支付:

java
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 合理设置限流参数

根据业务场景合理设置限流参数:

java
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 选择合适的限流策略

根据业务场景选择合适的限流策略:

java
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 解析器:

java
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 友好的错误提示

设置友好的错误提示信息:

java
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 限流异常处理

处理限流异常:

java
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 限流监控

记录限流日志:

java
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 限流不生效

问题:限流注解添加后不生效

解决方案

  1. 检查是否添加了 fanyi-spring-boot-starter-protection 依赖
  2. 检查 Redis 连接是否正常
  3. 检查方法是否是 public 方法
  4. 检查方法是否被 Spring 容器管理

7.2 限流参数不生效

问题:限流参数设置后不生效

解决方案

  1. 检查限流参数是否正确
  2. 检查 Redis 中是否已存在旧的限流配置
  3. 清除 Redis 中的限流配置

7.3 限流 Key 冲突

问题:不同方法的限流 Key 冲突

解决方案

  1. 使用不同的限流策略
  2. 使用表达式限流,指定不同的 keyArg
  3. 自定义限流 Key 解析器

7.4 限流性能问题

问题:限流影响接口性能

解决方案

  1. 检查 Redis 连接池配置
  2. 检查 Redis 性能
  3. 优化限流 Key 解析器

7.5 限流异常处理

问题:限流异常没有被正确处理

解决方案

  1. 检查全局异常处理器配置
  2. 检查限流异常类型
  3. 添加限流异常处理逻辑

8. 监控和调试

8.1 限流日志

记录限流日志:

java
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 限流统计

统计限流次数:

java
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 限流告警

配置限流告警:

java
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. 注意事项

  1. Redis 配置:确保 Redis 配置正确,连接正常
  2. 限流参数:根据业务场景合理设置限流参数
  3. 限流策略:根据业务场景选择合适的限流策略
  4. 限流 Key:确保限流 Key 唯一性,避免冲突
  5. 错误处理:正确处理限流异常
  6. 日志记录:记录限流日志,便于问题排查
  7. 性能优化:对于高频接口,优化限流 Key 解析器
  8. 监控告警:配置限流监控和告警
  9. 分布式环境:确保 Redis 配置支持分布式环境
  10. 限流测试:上线前进行限流测试,验证限流效果

10. 相关文档