在 Spring Boot 中实现请求速率限制的多种方案

本文将介绍几种在 Spring Boot 项目中实现请求速率限制的方案,包括使用 Spring Cloud Gateway、Nginx、Bucket4j、Spring AOP 和 Spring Interceptor。

使用 Spring Cloud Gateway 实现请求速率限制

1. 创建一个 Spring Boot 项目

首先,我们需要创建一个 Spring Boot 项目。确保 Spring Boot 版本为 2.7.x 或更高。

添加以下依赖到 pom.xml 文件中:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2. 配置 Spring Cloud Gateway

在项目中,我们需要配置 Spring Cloud Gateway 来启用速率限制功能。打开 application.yml 文件,添加以下配置:

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: rate_limit_route
          uri: http://httpbin.org:80  # 目标服务的 URL
          predicates:
            - Path=/get  # 匹配路径为 /get 的请求
          filters:
            - name: RequestRateLimiter  # 使用请求速率限制过滤器
              args:
                redis-rate-limiter.replenishRate: 10  # 每秒钟允许的请求数
                redis-rate-limiter.burstCapacity: 20  # 最大突发请求数

      redis:
        host: localhost  # Redis 服务器主机名
        port: 6379  # Redis 服务器端口

server:
  port: 8080  # 本地服务器端口

3. 添加 Redis 依赖

Spring Cloud Gateway 的速率限制功能依赖于 Redis 作为后端存储。确保你的项目中包含了 Redis 依赖。在 pom.xml 中添加以下依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

4. 运行 Redis

如果你还没有运行 Redis 实例,可以使用 Docker 快速启动一个 Redis 实例:

arduino 复制代码
docker run -d -p 6379:6379 redis

5. 启动 Spring Boot 应用

确保 Redis 服务已经启动,然后运行 Spring Boot 应用。应用启动后,Spring Cloud Gateway 将根据我们在 application.yml 中的配置,对路径为 /get 的请求进行速率限制。

6. 测试速率限制

可以使用 Postman 或 curl 工具来测试速率限制功能。例如,使用 curl 命令发送请求:

bash 复制代码
curl http://localhost:8080/get

在短时间内连续发送多次请求,超过配置的速率限制时,你会看到 429 Too Many Requests 的响应。

7. 自定义速率限制键生成器(可选)

如果需要基于更复杂的条件进行速率限制,可以自定义速率限制键生成器。创建一个实现 KeyResolver 接口的类,并注册为 Spring Bean。

自定义 Key Resolver 示例

创建一个自定义的 KeyResolver 类,用于基于 IP 地址进行速率限制:

kotlin 复制代码
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Configuration
public class RateLimiterConfig {

    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

application.yml 中引用自定义的 KeyResolver

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: rate_limit_route
          uri: http://httpbin.org:80
          predicates:
            - Path=/get
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                redis-rate-limiter.requestedTokens: 2
                key-resolver: "#{@userKeyResolver}"

8. 实现自定义限流逻辑(可选)

你也可以实现自定义限流逻辑,使用 RedisRateLimiter 的扩展性来满足更复杂的需求。例如,创建一个自定义的 RedisRateLimiter

typescript 复制代码
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import reactor.core.publisher.Mono;

@Configuration
public class CustomRateLimiterConfig {

    @Bean
    public RedisRateLimiter customRedisRateLimiter(ReactiveStringRedisTemplate redisTemplate) {
        return new RedisRateLimiter(10, 20) {
            @Override
            public Mono<Response> isAllowed(String routeId, String id) {
                // 自定义限流逻辑
                return super.isAllowed(routeId, id);
            }
        };
    }
}

application.yml 中引用自定义的 RedisRateLimiter

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: rate_limit_route
          uri: http://httpbin.org:80
          predicates:
            - Path=/get
          filters:
            - name: RequestRateLimiter
              args:
                rate-limiter: "#{@customRedisRateLimiter}"
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                redis-rate-limiter.requestedTokens: 2

使用 Nginx 实现请求速率限制

Nginx 是一个高性能的反向代理服务器和 Web 服务器,它具有内置的速率限制功能。

配置示例

在 Nginx 配置文件中,使用 limit_req_zonelimit_req 指令:

ini 复制代码
http {
    # 定义一个限速区域,基于客户端 IP 地址进行限速,每秒最多 10 个请求
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

    server {
        location /api/ {
            # 应用限速规则,允许最大 20 个突发请求
            limit_req zone=one burst=20;
            proxy_pass http://backend;
        }
    }
}

解释

  • limit_req_zone:定义限流区域,这里基于客户端 IP 地址进行限流,10r/s 表示每秒 10 个请求。
  • limit_req:应用速率限制,burst=20 表示允许的最大突发请求数。

使用 Bucket4j 实现请求速率限制

Bucket4j 是一个基于 Java 的速率限制库,可以与 Spring Boot 集成使用。

配置示例

添加依赖:

xml 复制代码
<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>6.2.0</version>
</dependency>

创建过滤器

java 复制代码
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;

@Component
public class RateLimitFilter extends OncePerRequestFilter {

    // 创建一个限速桶,允许每分钟 10 个请求
    private final Bucket bucket;

    public RateLimitFilter() {
        Bandwidth limit = Bandwidth.classic(10, Refill.greedy(10, Duration.ofMinutes(1)));
        this.bucket = Bucket.builder()
                .addLimit(limit)
                .build();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 如果请求可以从桶中获取到令牌,则继续处理请求
        if (bucket.tryConsume(1)) {
            filterChain.doFilter(request, response);
        } else {
            // 否则返回 429 Too Many Requests 状态码
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
        }
    }
}

注册过滤器

typescript 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class RateLimiterApplication {

    public static void main(String[] args) {
        SpringApplication.run(RateLimiterApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean<RateLimitFilter> rateLimitFilter() {
        FilterRegistrationBean<RateLimitFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new RateLimitFilter());
        registrationBean.addUrlPatterns("/api/*");
        return registrationBean;
    }
}

使用 Spring AOP 实现请求速率限制

通过 Spring AOP,我们可以在方法级别实现速率限制。

添加依赖

pom.xml 中添加 Spring AOP 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义注解和 AOP 切面

定义注解

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 自定义注解,用于标记需要限流的方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimited {
    int limit() default 5;  // 每时间段允许的请求数
    int timePeriod() default 60;  // 时间段长度,单位为秒
}

定义切面

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

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

@Aspect
@Component
public class RateLimiterAspect {

    // 存储限流器的映射,键为方法签名,值为限流器实例
    private Map<String, SimpleRateLimiter> limiters = new ConcurrentHashMap<>();

    // 环绕通知,应用于标记了 @RateLimited 注解的方法
    @Around("@annotation(rateLimited)")
    public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimited rateLimited) throws Throwable {
        String key = joinPoint.getSignature().toShortString();
        SimpleRateLimiter limiter = limiters.computeIfAbsent(key, k -> SimpleRateLimiter.create(rateLimited.limit(), rateLimited.timePeriod(), TimeUnit.SECONDS));
        if (limiter.tryAcquire()) {
            return joinPoint.proceed();  // 允许请求,继续执行方法
        } else {
            throw new RateLimitExceededException("Too many requests");  // 拒绝请求,抛出异常
        }
    }
}

定义简单的限流器

java 复制代码
import com.google.common.util.concurrent.RateLimiter;

import java.util.concurrent.TimeUnit;

public class SimpleRateLimiter {
    private final RateLimiter rateLimiter;

    // 私有构造函数,创建限流器实例
    private SimpleRateLimiter(double permitsPerSecond) {
        this.rateLimiter = RateLimiter.create(permitsPerSecond);
    }

    // 工厂方法,创建限流器实例
    public static SimpleRateLimiter create(int limit, int timePeriod, TimeUnit unit) {
        double permitsPerSecond = (double) limit / unit.toSeconds(timePeriod);
        return new SimpleRateLimiter(permitsPerSecond);
    }

    // 尝试获取一个令牌,成功则返回 true,否则返回 false
    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }
}

在控制器中使用注解

kotlin 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {

    @RateLimited(limit = 5, timePeriod = 60)  // 应用速率限制注解
    @GetMapping("/api/data")
    public String getData() {
        return "This is rate limited data.";  // 被限流的方法
    }
}

使用 Spring Interceptor 实现请求速率限制

通过 Spring 的拦截器也可以实现简单的速率限制。

配置示例

定义拦截器

java 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
public class RateLimiterInterceptor implements HandlerInterceptor {

    // 存储限流器的映射,键为客户端 IP 地址,值为限流器实例
    private ConcurrentHashMap<String, SimpleRateLimiter> limiters = new ConcurrentHashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String clientIp = request.getRemoteAddr();
        SimpleRateLimiter limiter = limiters.computeIfAbsent(clientIp, k -> SimpleRateLimiter.create(5, 60, TimeUnit.SECONDS));
        if (limiter.tryAcquire()) {
            return true;  // 允许请求
        } else {
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);  // 拒绝请求,返回 429 状态码
            return false;
        }
    }
}

注册拦截器

kotlin 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private RateLimiterInterceptor rateLimiterInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(rateLimiterInterceptor).addPathPatterns("/api/**");  // 为指定路径添加拦截器
    }
}

总结

本文介绍了多种在 Spring Boot 项目中实现请求速率限制的方法。根据具体的需求和项目环境,可以选择适合的方案来实现请求速率限制,确保后端服务的稳定性。

相关推荐
猿来入此小猿18 分钟前
基于SpringBoot在线音乐系统平台功能实现十七
java·spring boot·后端·毕业设计·音乐系统·音乐平台·毕业源码
重整旗鼓~1 小时前
2.flask中使用装饰器统一验证用户登录
后端·python·flask
bohu831 小时前
快速搭建springcloud 3.X+mybatis+nacos本地项目
spring cloud·nacos·mybatis
it噩梦1 小时前
springboot 工程使用proguard混淆
java·spring boot·后端
从种子到参天大树2 小时前
SpringBoot源码阅读系列(二):自动配置原理深度解析
后端
狠难说2 小时前
SpringCloud(八) - 自定义token令牌,鉴权(注解+拦截器),参数解析(注解+解析器)
后端
从种子到参天大树2 小时前
SpringBoot源码阅读系列(一):启动流程概述
后端
m0_748254882 小时前
Spring Boot实现多数据源连接和切换
spring boot·后端·oracle
庄周de蝴蝶3 小时前
一次 MySQL IF 函数的误用导致的生产小事故
后端·mysql
韩数3 小时前
Nping: 支持图表实时展示的多地址并发终端命令行 Ping
后端·rust·github