使用HandlerInterceptor和RateLimiter进行限流

    xiaoxiao2025-06-27  7

    使用HandlerInterceptor和RateLimiter进行限流

    为什么使用拦截器常见限流算法rateLimiter介绍注解创建拦截器springBoot拦截器配置开始使用 欢迎查看Eetal的第十九篇博客–使用HandlerInterceptor和RateLimiter进行限流

    为什么使用拦截器

    目前网上有博客使用的是aop,但是因为我项目里使用了shiro进行权限验证 shiro的过滤器链优先级是低于spring的过滤器的 所以根据约定优于配置,直接使用过滤器就不需要去配置shiro的一些映射和过滤器的优先级 同时网上博客基于aop为注解添加令牌桶效率的属性是有bug的,因为springMVC多线程是Method级别 而在aop的切面里创建rateLimiter或者为拿到的method对象的annotation对象绑定一个rateLimiter都会有线程问题 基于以上,把ratelimiter拿到拦截器,如果要定义不同rateLimiter可以改为定义多个拦截器和注解

    常见限流算法

    常见的限流方法有信号量计数器(Semaphore)计数当前线程个数 漏桶算法—使用一个容器保存进来的任务,按照固定速率流出任务,桶满时新加入的任务直接流出 令牌桶算法—使用一个容器存储令牌,按照固定速率生产令牌,令牌桶满时直接丢弃新生成令牌,任务进来以后尝试获取令牌,获取成功的任务开始执行 计数器较麻烦,需要维护实时记录线程数,完成时进行维护 对比令牌桶和漏桶,漏桶算法无法解决当容量空闲了一段时间以后,大量任务一起进来时,执行的平均效率低下的问题,而令牌桶的令牌生成速率是固定的

    rateLimiter介绍

    RateLimiter是guava提供的基于令牌桶算法的实现类 create(Double permitsPerSecond)方法根据给定的(令牌:单位时间(1s))比例为令牌生成速率 tryAcquire()方法尝试获取一个令牌,立即返回true/false,不阻塞,重载方法具备设置获取令牌个数、获取最大等待时间等参数 acquire()方法与tryAcquire类似,但是会阻塞,尝试获取一个令牌,没有时则阻塞直到获取成功

    注解创建

    @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimit { /** * 获取令牌的等待时间 默认0 * @return */ int value() default 0; /** * 超时时间单位 * @return */ TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS; }

    拦截器

    public class RateLimitInterceptor implements HandlerInterceptor { private final static Logger logger = LoggerFactory.getLogger(RateLimitInterceptor.class); //每s产生2000个令牌 RateLimiter rateLimiter = RateLimiter.create(2000); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果是SpringMVC请求 if(handler instanceof HandlerMethod){ HandlerMethod handlerMethod = (HandlerMethod) handler; RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class); if(rateLimit != null) { logger.info("rateLimit intercept method..."); if (!rateLimiter.tryAcquire(rateLimit.value(), rateLimit.timeOutUnit())) { throw new Exception("系统繁忙,新稍后再试!"); //这里抛出异常是因为我项目里对异常进行了全局处理 } } } return true; } }

    springBoot拦截器配置

    @Configuration public class WebMVCConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RateLimitInterceptor()).addPathPatterns("/**") .excludePathPatterns("/static/**", "/"); //配置限流拦截器拦截以及不拦截的映射 } }

    开始使用

    @RateLimit(1000) //最大等待时间1s @RequestMapping("/login") public ModelAndView login(User user) { ModelAndView mv = new ModelAndView(); String username=user.getUsername(); String password=user.getPassword(); UsernamePasswordToken token = new UsernamePasswordToken(username,password,false); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); } catch(IncorrectCredentialsException e){ mv.addObject("msg", "密码错误"); mv.setViewName("/login.html"); return mv; } catch (AuthenticationException e) { mv.addObject("msg", "登录失败"); mv.setViewName("/login.html"); return mv; } mv.addObject("userDto",(UserDto)subject.getSession().getAttribute("userDto")); mv.setViewName("/index.html"); return mv; }

    更多文章,请搜索公众号歪歪梯Club

    最新回复(0)