SpringBoot如何通过拦截器实现接口限流

在Spring Boot中,可以通过自定义拦截器(Interceptor)结合Redis或内存计数器实现接口限流。以下是两种典型实现方式及代码示例:


​方案一:基于Redis + Lua脚本的分布式限流​

​核心逻辑​

  1. Redis配置:使用Lua脚本保证原子性操作(计数+过期时间设置)。
  2. 拦截器:拦截请求,通过Redis统计IP或用户维度的访问次数。
  3. 注册拦截器:指定拦截路径和排除路径。

​代码实现​

1. Redis配置类
swift 复制代码
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

    @Bean
    public DefaultRedisScript<Long> rateLimitScript() {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(
            "local key = KEYS[1]\n" +
            "local limit = tonumber(ARGV[1])\n" +
            "local expire = tonumber(ARGV[2])\n" +
            "local current = redis.call('INCR', key)\n" +
            "if current == 1 then\n" +
            "    redis.call('EXPIRE', key, expire)\n" +
            "end\n" +
            "return current > limit and 1 or 0"
        );
        script.setResultType(Long.class);
        return script;
    }
}
2. 限流拦截器
java 复制代码
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private DefaultRedisScript<Long> rateLimitScript;

    private static final int DEFAULT_LIMIT = 60; // 每分钟60次
    private static final int DEFAULT_TIMEOUT = 60; // 60秒过期

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        String ip = request.getRemoteAddr();
        String uri = request.getRequestURI();
        String key = "rate_limit:" + ip + ":" + uri.split("/")[1]; // 按接口前缀分组

        Long result = redisTemplate.execute(
            rateLimitScript,
            Collections.singletonList(key),
            DEFAULT_LIMIT, DEFAULT_TIMEOUT
        );

        if (result != null && result == 1) {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("Too many requests");
            return false;
        }
        return true;
    }
}
3. 注册拦截器
typescript 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private RateLimitInterceptor rateLimitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(rateLimitInterceptor)
            .addPathPatterns("/api/**")
            .excludePathPatterns("/api/login");
    }
}

​方案二:基于内存计数器的单机限流​

​核心逻辑​

  1. 拦截器 :使用ConcurrentHashMap存储IP和访问时间戳。
  2. 滑动窗口:统计1分钟内的请求数,超限则拒绝。

​代码实现​

java 复制代码
public class RateLimitingInterceptor implements HandlerInterceptor {
    private final ConcurrentMap<String, Long> requestCounts = new ConcurrentHashMap<>();
    private static final long ALLOWED_REQUESTS_PER_MINUTE = 60;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String clientIp = request.getRemoteAddr();
        long currentTime = System.currentTimeMillis();

        // 清理过期请求
        requestCounts.entrySet().removeIf(entry -> 
            currentTime - entry.getValue() > TimeUnit.MINUTES.toMillis(1)
        );

        // 统计当前窗口请求数
        long count = requestCounts.values().stream()
            .filter(timestamp -> currentTime - timestamp < TimeUnit.MINUTES.toMillis(1))
            .count();

        if (count >= ALLOWED_REQUESTS_PER_MINUTE) {
            response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
            response.getWriter().write("请求过于频繁");
            return false;
        }

        requestCounts.put(clientIp, currentTime);
        return true;
    }
}

​关键对比与选择建议​

​方案​ ​适用场景​ ​优点​ ​缺点​
Redis + Lua 分布式环境,高精度限流 原子性操作,支持分布式,可动态调整参数 依赖Redis,网络开销较大
内存计数器 单机环境,简单场景 无外部依赖,实现简单 不支持分布式,重启后数据丢失

​扩展建议​:

  • 动态配置 :将限流参数(如DEFAULT_LIMIT)改为从配置中心读取。
  • 注解化 :结合自定义注解(如@RateLimit)实现更灵活的限流规则。

两种方案均能有效实现接口限流,根据项目需求选择即可。

相关推荐
我的div丢了肿么办7 分钟前
进程和线程以及go语言中的协程
后端·go
墨家句子10 分钟前
4G 显存老显卡怎么跑模型:llama.cpp 加 GGUF 配置
linux·后端
HLAIA光子11 分钟前
RAG Chunks 切分优化后成本骤降 86%
后端·性能优化·架构
后端LV17 分钟前
Caffeine 快到 1450 万 ops/s,为什么我还是不敢说数据一致
java·后端
小小张说故事30 分钟前
Python 装饰器把函数"搞丢"了?5 个必踩的坑与完整避坑对照表
后端·python
大圣编蚕2 小时前
Spring AOP 失效排查:从原理到实战的完整指南
java·后端·spring
AINative软件工程2 小时前
LLM 多租户 Prompt Isolation 工程实践:为 100 个租户定制 Prompt,但别让它们互相污染
后端·llm
EatFan2 小时前
MCP 从概念到落地:Java(Spring AI Alibaba)与 .NET 双栈接入实操对比
java·人工智能·后端·spring·.net·java后端·mcp
专业程序开发源2 小时前
springboot全民健身和饮食健康管理系统29158-计算机课程设计、毕业设计
java·spring boot·后端·python·django·php·课程设计
梦帮科技3 小时前
【3.0修订版】 RNS 代币架构:ERC20 五件套扩展与六钱包分配
数据结构·后端·算法·架构·node.js·区块链·php