Spring AOP + Redisson 实现基于注解的分布式限流方案

1. 背景与需求

在高并发系统中,为了防止接口被频繁调用导致服务器过载,我们通常需要对接口进行限流。常见的限流方案包括:

  • 固定窗口滑动窗口令牌桶等算法

  • 基于本地缓存(如 Guava RateLimiter

  • 基于分布式缓存(如 Redis + Lua 脚本)

本文将演示一种简单且高效的实现方案:
使用 Spring AOP + 自定义注解 + Redisson 分布式限流器 ,实现用户/IP/API 维度的注解式限流。

核心目标:

  • 通过注解轻松配置接口的限流规则。

  • 支持接口级别用户级别IP级别限流。

  • 使用 Redisson 简化 Redis 分布式锁与限流操作。


2. 技术选型

技术 作用
Spring AOP 切面编程,拦截带有 @RateLimit 注解的方法
Redisson 对 Redis 操作的高级封装,提供分布式限流器 RRateLimiter
Redis 存储限流状态,支持集群部署,天然分布式

3. 依赖引入

pom.xml 中添加 Redisson 依赖:

XML 复制代码
<!-- Redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.50.0</version>
</dependency>

4. 配置 Redis

application.yml 中配置 Redis 连接信息:

XML 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379
      password:
      ttl: 3600
      database: 0

5. Redisson 客户端配置

使用 @Configuration 定义 RedissonClient

java 复制代码
@Configuration
public class RedissonConfig {

    @Value("${spring.data.redis.host}")
    private String redisHost;

    @Value("${spring.data.redis.port}")
    private Integer redisPort;

    @Value("${spring.data.redis.password}")
    private String redisPassword;

    @Value("${spring.data.redis.database}")
    private Integer redisDatabase;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        String address = "redis://" + redisHost + ":" + redisPort;
        SingleServerConfig singleServerConfig = config.useSingleServer()
                .setAddress(address)
                .setDatabase(redisDatabase)
                .setConnectionMinimumIdleSize(1)
                .setConnectionPoolSize(10)
                .setIdleConnectionTimeout(30000)
                .setConnectTimeout(5000)
                .setTimeout(3000)
                .setRetryAttempts(3)
                .setRetryInterval(1500);
        // 如果有密码则设置密码
        if (redisPassword != null && !redisPassword.isEmpty()) {
            singleServerConfig.setPassword(redisPassword);
        }
        return Redisson.create(config);
    }
}

6. 自定义注解

通过注解声明限流规则:

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {

    // 限流key前缀
    String key() default "";

    // 每个时间窗口允许的请求数
    int rate() default 10;

    // 时间窗口(秒)
    int rateInterval() default 1;

    // 限流类型:API、USER、IP
    RateLimitType limitType() default RateLimitType.USER;

    // key过期时间(小时)
    int expireTime() default 1;

    // 提示信息
    String message() default "请求过于频繁,请稍后再试";
}

限流类型枚举:

java 复制代码
public enum RateLimitType {
    API,   // 接口级别
    USER,  // 用户级别
    IP     // IP级别
}

7. AOP 切面实现

使用切面在方法调用前执行限流逻辑:

java 复制代码
@Aspect
@Component
@Slf4j
public class RateLimitAspect {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private UserService userService;

    @Before("@annotation(rateLimit)")
    public void doBefore(JoinPoint point, RateLimit rateLimit) {
        // 生成限流Key
        String key = generateRateLimitKey(point, rateLimit);
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
        rateLimiter.expire(Duration.ofHours(rateLimit.expireTime()));
        // 设置限流参数:时间窗口内允许请求数
        rateLimiter.trySetRate(RateType.OVERALL, rateLimit.rate(), rateLimit.rateInterval(), RateIntervalUnit.SECONDS);

        // 尝试获取令牌
        if (!rateLimiter.tryAcquire(1)) {
            throw new BusinessException(ErrorCode.TOO_MANY_REQUEST, rateLimit.message());
        }
    }

    private String generateRateLimitKey(JoinPoint point, RateLimit rateLimit) {
        StringBuilder keyBuilder = new StringBuilder("rate_limit:");
        if (!rateLimit.key().isEmpty()) {
            keyBuilder.append(rateLimit.key()).append(":");
        }
        switch (rateLimit.limitType()) {
            case API:
                MethodSignature signature = (MethodSignature) point.getSignature();
                Method method = signature.getMethod();
                keyBuilder.append("api:").append(method.getDeclaringClass().getSimpleName())
                          .append(".").append(method.getName());
                break;
            case USER:
                try {
                    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                    if (attributes != null) {
                        HttpServletRequest request = attributes.getRequest();
                        User loginUser = userService.getLoginUser(request);
                        keyBuilder.append("user:").append(loginUser.getId());
                    } else {
                        keyBuilder.append("ip:").append(getClientIP());
                    }
                } catch (BusinessException e) {
                    keyBuilder.append("ip:").append(getClientIP());
                }
                break;
            case IP:
                keyBuilder.append("ip:").append(getClientIP());
                break;
            default:
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "不支持的限流类型");
        }
        return keyBuilder.toString();
    }

    private String getClientIP() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) return "unknown";
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        log.info("客户端IP: {}", ip);
        return ip != null ? ip : "unknown";
    }
}

8. 使用示例

在需要限流的接口上添加注解即可:

java 复制代码
@RestController
@RequestMapping("/api/test")
public class TestController {

    // 用户级别限流:每秒最多 5 次
    @RateLimit(rate = 5, rateInterval = 1, limitType = RateLimitType.USER, message = "操作太频繁啦,请稍后重试!")
    @GetMapping("/userLimit")
    public String userLimit() {
        return "用户级别限流测试成功";
    }

    // IP级别限流:每分钟 20 次
    @RateLimit(rate = 20, rateInterval = 60, limitType = RateLimitType.IP)
    @GetMapping("/ipLimit")
    public String ipLimit() {
        return "IP级别限流测试成功";
    }

    // 接口级别限流:每秒 10 次
    @RateLimit(rate = 10, rateInterval = 1, limitType = RateLimitType.API)
    @GetMapping("/apiLimit")
    public String apiLimit() {
        return "接口级别限流测试成功";
    }
}

9. 工作流程

  1. 注解声明规则 :开发者在 Controller 方法上标记 @RateLimit,指定key前缀、速率、时间窗口、key过期时间和限流类型等(未指定将使用默认值)。

  2. AOP 拦截请求:切面拦截注解方法,在方法执行前通过 Redisson 进行令牌检查。

  3. Redis 记录状态:Redisson 将限流数据存入 Redis,实现分布式共享。

  4. 限流生效:若未获取到令牌,直接抛出自定义异常并返回提示信息。


10. 总结

  • 优点

    • 使用注解即可快速启用限流,低侵入。

    • Redisson 提供分布式保证,适用于集群环境。

    • 支持多种维度(API、用户、IP)灵活配置。

  • 适用场景

    • 接口防刷、防爬虫

    • 高频操作(如短信发送、验证码请求)

    • 防止恶意用户短时间内发起过多请求

这套方案简单易用,可作为分布式系统中的通用限流基础组件。

若想更精细的控制限流规则请参考另几篇文章:

https://blog.csdn.net/xundefined/article/details/144200495

https://blog.csdn.net/xundefined/article/details/144300779


通过 Spring AOP + Redisson 的组合,我们可以在不改动业务逻辑的前提下,快速实现高性能、可扩展的分布式限流,让接口在高并发场景下更安全、更稳定。

相关推荐
渣哥2 小时前
用错了就翻车!Thread.sleep() vs Thread.yield() 的区别,很多人都踩过坑
java
孟意昶2 小时前
Spark专题-第二部分:Spark SQL 入门(5)-算子介绍-Join
大数据·分布式·sql·spark·big data
风槐啊2 小时前
邪修实战系列(6)
java·ide·windows·spring boot·状态模式
£漫步 云端彡3 小时前
docker常用命令
java·docker·eureka
掘金-我是哪吒3 小时前
分布式微服务系统架构第172集:Kafka、MongoDB、Redis、MySQL
redis·分布式·微服务·kafka·系统架构
Brookty3 小时前
【算法】滑动窗口(一)-长度最小的子数组
java·学习·算法·力扣·滑动窗口
JAVA学习通3 小时前
微服务项目->在线oj系统(Java-Spring)----6.0
java·开发语言·spring
调皮的木木3 小时前
架构师成长之路 04:缓存进阶避坑:Value 存储防污染、数据同步最优方案,再也不怕宕机拖垮 DB
redis·缓存·系统架构
袁煦丞 cpolar内网穿透实验室3 小时前
Remote JVM Debug远程给Java程序“做手术”!cpolar内网穿透实验室第626个成功挑战
java·开发语言·jvm·远程工作·内网穿透·cpolar