使用 Spring AOP 和 Guava RateLimiter 实现 API 限流

在高并发的应用场景下,合理的限流策略是保证系统稳定性的重要手段之一。限流可以防止系统资源被耗尽,避免雪崩效应的发生。本文将介绍如何使用 Spring AOP 和 Guava RateLimiter 实现API限流,并支持自定义限流超时时间。

引入依赖

首先,需要在 pom.xml 中引入 Guava 依赖:

xml 复制代码
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

定义注解

其次,定义一个 @ApiRateLimit 注解,在需要限流的方法上标注该注解:

java 复制代码
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiRateLimit {
    double qps() default 1; // 每秒钟生成令牌的速率
    long timeout() default 0; // 尝试获取令牌的超时时间
    TimeUnit timeUnit() default TimeUnit.SECONDS; // 超时时间单位
}
  • qps 参数控制每秒生成令牌数,即控制限流速率
  • timeouttimeUnit 参数控制获取令牌的超时时间,0表示无超时

实现切面

接下来实现 ApiRateLimitAspect 切面类,在方法执行前通过 RateLimiter 判断是否被限流:

java 复制代码
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class ApiRateLimitAspect {

    private final Map<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();

    @Before("@annotation(apiRateLimit)")
    public void limit(JoinPoint joinPoint, ApiRateLimit apiRateLimit) {
        String methodName = joinPoint.getSignature().toLongString();
        double qps = apiRateLimit.qps();
        RateLimiter limiter = rateLimiters.computeIfAbsent(methodName, k -> RateLimiter.create(qps));
        long timeout = apiRateLimit.timeout();
        TimeUnit timeUnit = apiRateLimit.timeUnit();
        if (timeout > 0) {
            if (!limiter.tryAcquire(timeout, timeUnit)) {
                throw new RuntimeException("API rate limit exceeded");
            }
        } else {
            if (!limiter.tryAcquire()) {
                throw new RuntimeException("API rate limit exceeded");
            }
        }
    }
}
  • 使用 ConcurrentHashMap 缓存每个方法对应的 RateLimiter 实例
  • 根据 @ApiRateLimit 注解的参数尝试获取令牌
    • 若超时时间大于0,使用 tryAcquire(timeout, timeUnit) 获取令牌
    • 若超时时间为0,使用 tryAcquire() 获取令牌
  • 如果无法获取令牌,抛出 RuntimeException 限流异常

使用示例

在 Controller 方法上标注 @ApiRateLimit 注解即可实现限流:

java 复制代码
@RestController
public class DemoController {

    @GetMapping("/test")
    @ApiRateLimit(qps = 2, timeout = 200, timeUnit = TimeUnit.MILLISECONDS)
    public String test() {
        return "hello world";
    }
}

上述代码对 /test 接口限流,限流速率为每秒 2 个请求,获取令牌超时时间为 200 毫秒。

总结

通过简单的注解和 AOP 切面,就可以实现 API 限流功能,并支持自定义限流速率和限流超时时间。这种实现方式无侵入性,添加或移除限流只需要在方法上增加或移除注解即可,降低了维护成本。

值得注意的是,在分布式环境下,单机限流的方式可能无法满足需求,我们需要结合分布式限流组件如 Redis 等来实现全局限流。另外,限流策略也可以根据实际场景进行优化,如配合熔断、降级等策略使用,以提高系统的稳定性。

相关推荐
天若有情6734 小时前
【java EE】IDEA 中创建或迁移 Spring 或 Java EE 项目的核心步骤和注意事项
后端·spring·java-ee·intellij-idea
大鱼七成饱6 小时前
💥 从崩溃到稳定:我踩过的 Rust Tokio 线程池坑(含代码示例)
后端
喵个咪6 小时前
开箱即用的GO后台管理系统 Kratos Admin - 站内信
后端·微服务·go
韩立学长6 小时前
基于Springboot的旧物公益捐赠管理系统3726v22v(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
Dyan_csdn7 小时前
springboot系统设计选题3
java·spring boot·后端
Yeats_Liao8 小时前
时序数据库系列(二):InfluxDB安装配置从零搭建
数据库·后端·时序数据库
Yeats_Liao8 小时前
时序数据库系列(一):InfluxDB入门指南核心概念详解
数据库·后端·时序数据库·db
蓝-萧8 小时前
springboot系列--自动配置原理
java·后端
bobogift9 小时前
【玩转全栈】----Django基本配置和介绍
java·后端
倚栏听风雨9 小时前
Async-Profiler 框架简介
后端