You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非Spring Boot/Cloud环境下Spring MVC应用REST API限流实现问询

传统Spring(非Boot/Cloud)+ Tomcat应用的REST API限流实现方案

一、本地限流(单Tomcat实例场景)

适合单实例部署的应用,用本地缓存实现限流逻辑,无需额外中间件。

方案1:Spring HandlerInterceptor + Guava RateLimiter

利用Spring MVC的拦截器机制,结合Guava的令牌桶实现精细化接口限流。

  1. 引入Guava依赖(Maven)
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version> <!-- 选择适配项目的版本 -->
</dependency>
  1. 定义限流注解
    用注解标记需要限流的接口,灵活配置限流规则:
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    // 每秒允许的请求次数
    double permitsPerSecond();
}
  1. 实现限流拦截器
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class RateLimitInterceptor implements HandlerInterceptor {

    // 存储不同接口的限流器,key为方法全限定名
    private final Map<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class);
        if (rateLimit == null) {
            // 无注解的接口跳过限流
            return true;
        }

        String methodKey = handlerMethod.getBeanType().getName() + "." + handlerMethod.getMethod().getName();
        RateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(methodKey, k -> RateLimiter.create(rateLimit.permitsPerSecond()));

        if (!rateLimiter.tryAcquire()) {
            // 触发限流,返回429状态码
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            response.getWriter().write("Too many requests, please try again later.");
            return false;
        }
        return true;
    }
}
  1. 注册拦截器
    如果用XML配置(spring-servlet.xml):
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/api/**"/> <!-- 仅对API路径生效 -->
        <bean class="com.yourpackage.RateLimitInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

如果用Java配置:

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 WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RateLimitInterceptor())
                .addPathPatterns("/api/**");
    }
}
  1. 在接口上使用注解
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/user")
public class UserController {

    @RateLimit(permitsPerSecond = 5) // 每秒允许5次请求
    @GetMapping("/info")
    public String getUserInfo() {
        return "User info";
    }
}

方案2:Servlet Filter实现限流

更底层的Servlet级限流,适合不需要Spring拦截器的场景:

import com.google.common.util.concurrent.RateLimiter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@WebFilter(filterName = "rateLimitFilter", urlPatterns = "/api/**")
public class RateLimitFilter implements Filter {

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

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String requestURI = ((javax.servlet.http.HttpServletRequest) request).getRequestURI();
        
        // 按请求URI分组限流,每秒10次
        RateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(requestURI, k -> RateLimiter.create(10));

        if (!rateLimiter.tryAcquire()) {
            httpResponse.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            httpResponse.getWriter().write("Too many requests");
            return;
        }
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}
}

如果不用@WebFilter,可在web.xml中配置:

<filter>
    <filter-name>rateLimitFilter</filter-name>
    <filter-class>com.yourpackage.RateLimitFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>rateLimitFilter</filter-name>
    <url-pattern>/api/**</url-pattern>
</filter-mapping>

二、分布式限流(多Tomcat实例场景)

多实例部署时,本地限流无法全局生效,需用Redis+Lua脚本实现分布式原子限流。

  1. 引入Jedis依赖(Maven)
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.6</version>
</dependency>
  1. 实现Redis限流工具类
    用Lua脚本保证计数和过期时间的原子性:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Collections;

public class RedisRateLimiter {

    private static final String LIMIT_KEY_PREFIX = "rate_limit:";
    private static final String REJECTED = "0";
    private static final String ALLOWED = "1";

    private final JedisPool jedisPool;

    public RedisRateLimiter(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    // key:限流标识(如用户ID+接口路径);maxRequests:周期内最大请求数;periodSeconds:周期(秒)
    public boolean tryAcquire(String key, int maxRequests, int periodSeconds) {
        try (Jedis jedis = jedisPool.getResource()) {
            String limitKey = LIMIT_KEY_PREFIX + key;
            String script = """
                    local current = redis.call('incr', KEYS[1])
                    if current == 1 then
                        redis.call('expire', KEYS[1], ARGV[1])
                    end
                    if current > tonumber(ARGV[2]) then
                        return %s
                    end
                    return %s
                    """.formatted(REJECTED, ALLOWED);

            String result = jedis.eval(script, Collections.singletonList(limitKey),
                    Collections.singletonList(String.valueOf(periodSeconds)),
                    Collections.singletonList(String.valueOf(maxRequests)));
            return ALLOWED.equals(result);
        }
    }
}
  1. 实现分布式限流拦截器
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class DistributedRateLimitInterceptor implements HandlerInterceptor {

    private final RedisRateLimiter redisRateLimiter;

    public DistributedRateLimitInterceptor(RedisRateLimiter redisRateLimiter) {
        this.redisRateLimiter = redisRateLimiter;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class);
        if (rateLimit == null) {
            return true;
        }

        // 用用户ID+方法签名作为限流key,避免不同用户互相影响
        String userId = request.getHeader("X-User-Id");
        String methodKey = handlerMethod.getBeanType().getName() + "." + handlerMethod.getMethod().getName();
        String limitKey = userId != null ? userId + ":" + methodKey : methodKey;

        boolean allowed = redisRateLimiter.tryAcquire(limitKey, (int) rateLimit.permitsPerSecond(), 1);
        if (!allowed) {
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            response.getWriter().write("Too many requests, please try again later.");
            return false;
        }
        return true;
    }
}
  1. 注册Redis连接池和拦截器
    XML配置示例:
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
    <constructor-arg index="0" value="your-redis-host"/>
    <constructor-arg index="1" value="6379"/>
</bean>

<bean id="redisRateLimiter" class="com.yourpackage.RedisRateLimiter">
    <constructor-arg ref="jedisPool"/>
</bean>

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/api/**"/>
        <bean class="com.yourpackage.DistributedRateLimitInterceptor">
            <constructor-arg ref="redisRateLimiter"/>
        </bean>
    </mvc:interceptor>
</mvc:interceptors>

内容的提问来源于stack exchange,提问作者shree

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 02:16:11