开发者

SpringBoot通过拦截器实现接口限流的两种方案

开发者 https://www.devze.com 2025-10-12 10:22 出处:网络 作者: IT橘子皮
目录​方案一:基于Redis + Lua脚本的分布式限流​​核心逻辑​​代码实现​1. Redis配置类2. 限流拦截器3. 注册拦截器​方案二:基于内存计数器的单机限流​​核心逻辑​​代码实现​​关键对比与选择建议​在Sprin
目录
  • ​方案一:基于Redis + Lua脚本的分布式限流​
    • ​核心逻辑​
    • ​代码实现​
      • 1. Redis配置类
      • 2. 限流拦截器
      • 3. 注册拦截器
  • ​方案二:基于内存计数器的单机限流​
    • ​核心逻辑​
      • ​代码实现​
      • ​关键对比与选择建议​

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

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

        ​核心逻辑​

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

        ​代码实现​

        1. Redis配置类

        @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.setScripphptTehttp://www.devze.comxt(
                    "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" +
         python      www.devze.com     "end\n" +
                    "return current > limit and 1 or 0"
                );
                script.setResultType(Long.class);
                return script;
            }
        }
        

        2. 限流拦截器

        @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. 注册拦截器

        @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分钟内的请求数,超限则拒绝。

        ​代码实现​

        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)实现更灵活的限流规则。

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

        到此这篇关于SpringBoot通过拦截器实现接口限流的两种方案的文章就介绍到这了,更多相关SpringBoot拦截器接口限流内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

        0

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        关注公众号