常见的限流工具有,guava限流和hystrix限流。他们的区别是,guava是服务的提供方防止自身因为请求过多崩溃而限流。hystrix是服务调用方防止后端业务响应异常,造成自身雪崩效应,进行熔断和降级处理。guava提供了令牌桶算法来实现限流,有没有发现redis也有一个令牌桶算法
1、pom文件添加依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
2、配置文件application-dev.yml添加
#限流器QPS设置
rateLimiter:
all: 300
resource: 100
interface: 1
3、在com.example.config包下添加限流器配置类RateLimiterConfig.java
package com.example.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.util.concurrent.RateLimiter;
@Configuration
public class RateLimiterConfig {
@Value("${rateLimiter.all}")
private String rateLimiter1;
@Value("${rateLimiter.resource}")
private String rateLimiter2;
@Value("${rateLimiter.interface}")
private String rateLimiter3;
/**
* 全局限流器
* @return
*/
@Bean
public RateLimiter rateLimiterAll() {
RateLimiter rateLimiterAll = RateLimiter.create(Double.parseDouble(rateLimiter1));
return rateLimiterAll;
}
/**
* 针对单个resource限流器
* @return
*/
@Bean
public RateLimiter rateLimiterResource() {
RateLimiter rateLimiterResource = RateLimiter.create(Double.parseDouble(rateLimiter2));
return rateLimiterResource;
}
/**
* 针对单个接口限流器
* @return
*/
@Bean
public RateLimiter rateLimiterIngerface() {
RateLimiter rateLimiterIngerface = RateLimiter.create(Double.parseDouble(rateLimiter3));
return rateLimiterIngerface;
}
}
4、在包com.example.config下添加自定义拦截器RateLimitInterceptor.java
package com.example.config;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.google.common.util.concurrent.RateLimiter;
public class RateLimitInterceptor implements HandlerInterceptor {
protected static Logger logger = LoggerFactory.getLogger(RateLimitInterceptor.class);
@Autowired
RateLimiter rateLimiterAll;
@Autowired
RateLimiter rateLimiterResource;
@Autowired
RateLimiter rateLimiterIngerface;
/**
* 在请求处理之前进行调用(Controller方法调用之前)
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取HandlerMethod对象
HandlerMethod h = (HandlerMethod)handler;
//尝试获取一个令牌,带超时时间,超时时间单位为毫秒
if (!rateLimiterAll.tryAcquire(100, TimeUnit.MICROSECONDS)) {
logger.info("全局限流器限流中...");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html; charset=UTF-8");
response.getWriter().write("{\"data\":\"访问次数受限\",\"sign\":null,\"repCode\":\"999999\",\"repMsg\":\"失败\"}");
response.getWriter().flush();
response.getWriter().close();
return false;
} else {
logger.info("全局限流器通过...");
}
//resource限流器
if (h.getBean().getClass().getSimpleName().equals("MyController")) {
if (!rateLimiterResource.tryAcquire(100, TimeUnit.MICROSECONDS)) {
logger.info("resource限流器限流中...");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html; charset=UTF-8");
response.getWriter().write("{\"data\":\"访问次数受限\",\"sign\":null,\"repCode\":\"999999\",\"repMsg\":\"失败\"}");
response.getWriter().flush();
response.getWriter().close();
return false;
} else {
logger.info("resource限流器通过...");
}
}
//接口限流器
if(h.getMethod().getName().equals("hello")) {
if (!rateLimiterIngerface.tryAcquire(100, TimeUnit.MICROSECONDS)) {
logger.info("接口限流器限流中...");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type", "text/html; charset=UTF-8");
response.getWriter().write("{\"data\":\"访问次数受限\",\"sign\":null,\"repCode\":\"999999\",\"repMsg\":\"失败\"}");
response.getWriter().flush();
response.getWriter().close();
return false;
} else {
logger.info("接口限流器通过...");
}
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
/**
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
5、在包com.example.config下添加拦截器配置类WebMvcConfigurer.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器配置类
* @author User
*
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//在此处,将拦截器注册为一个Bean,才能使用@Autowired注解注入对象
@Bean
public RateLimitInterceptor rateLimitInterceptor() {
return new RateLimitInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册RateLimitInterceptor拦截器
// 要注意这里使用this.rateLimitInterceptor()
InterceptorRegistration registration = registry.addInterceptor(this.rateLimitInterceptor());
// 所有路径都被拦截
registration.addPathPatterns("/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
6、说明
1)要将拦截器注册为一个Bean,才能在拦截器里使用@Autowired注解注入对象
2)HttpServletResponse返回的报文头要设置成"text/html; charset=UTF-8",否则页面显示会变成小小的字体
7、测试
访问测试地址http://localhost:8088/hello
正常通过:{"data":"hello world!","sign":null,"repCode":"000000","repMsg":"成功"}
限流拒绝:{"data":"访问次数受限","sign":null,"repCode":"999999","repMsg":"失败"}
8、问题
1)我是想定义3个限流器,针对全局的、针对单个resource的、针对单个接口的,但是怎么在拦截器里针对单个resource和接口限流呢?如何获取到请求的是哪个resource和接口?
解决:从拦截器preHandle方法的第三个参数Object handler获取
2)但是问题是resource限流器和接口限流器是公用的,怎么让每个controller和接口有各自的限流器呢?
9、spring启动时,就把请求url和controller映射关系保存在容器中
拦截器的preHandle方法传入3个参数
request:是指经过spring封装的请求对象,包含请求地址、头、参数、body(流)等信息
response:是指经过spring封装的响应对象,包含输入流、响应body类型等信息
handler:是HandlerMethod对象,包含了调用哪个controller信息和调用的方法信息
参考资料:
https://blog.csdn.net/qq_32352777/article/details/88857728
https://cloud.tencent.com/developer/article/1773878
https://springboot.io/t/topic/2352
HandlerMethod
https://blog.csdn.net/qq_41933149/article/details/102491476
https://blog.csdn.net/x763795151/article/details/88782330
注:最新代码上传至https://github.com/csj50/myboot