使用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