在SpringBoot中使用Guava基于令牌桶实现限流,就是这么流批!
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
关注公众号后台回复pay或mall获取实战项目资料+视频
作者:KevinBlandy
https://springboot.io/t/topic/2352
限流说详细了,名堂也多。这种算法那种算法,这种策略那种策略的。没有绝对的银弹。都要结合实际的场景来实现。最简单的,使用Google的Guava,几行代码。就可以优雅的对一个接口完成限流。
令牌桶算法
通俗的理解就是,有一个固定大小的水桶,水龙头一直按照一定的频率往里面滴水。水满了,就不滴了。客户端每次进行请求之前,都要先尝试从水桶里面起码取出“一滴水”,才能处理业务。因为桶的大小固定,水龙头滴水频率固定。从而也就保证了数据接口的访问流量。
Guava
谷歌的一个工具库,包含了大量的Java工具类,像hash算法,字符串处理,集合等等。。。
https://github.com/google/guava
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
速率限制器 RateLimiter
/**
* 创建一个限速器,每1秒,产生2.5个令牌
*/
RateLimiter rateLimiter = RateLimiter.create(2.5, 1, TimeUnit.SECONDS);
/**
* 尝试获取1个令牌,如果没有,会阻塞当前线程。直到获取成功返回。
* 返回值是,阻塞的秒数
*/
double waitSeconds = rateLimiter.acquire();
/**
* 尝试获取1个令牌,不会阻塞当前线程。
* 立即返回是否获取成功。
*/
boolean success = rateLimiter.tryAcquire();
好了,这就是核心代码。就3行。首先创建一个限速器,指定令牌的生产频率。核心的方法就是2种,阻塞获取令牌,非阻塞获取令牌。代码也通俗易懂。
重载方法
不论是阻塞获取令牌还是非阻塞获取令牌,它们都有几个重载方法。一看也清楚,就是可以设置获取令牌的数量,以及阻塞的时间。
public double acquire(int permits)
public boolean tryAcquire(Duration timeout)
public boolean tryAcquire(int permits)
public boolean tryAcquire(long timeout, TimeUnit unit)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
public boolean tryAcquire(int permits, Duration timeout)
Controller ,也就是被限速的接口
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping
public Object test () {
return Collections.singletonMap("success", "true");
}
}
RateLimiterInterceptor,负责实现限速逻辑
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterInterceptor extends HandlerInterceptorAdapter {
private final RateLimiter rateLimiter;
/**
* 通过构造函数初始化限速器
*/
public RateLimiterInterceptor(RateLimiter rateLimiter) {
super();
this.rateLimiter = rateLimiter;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(this.rateLimiter.tryAcquire()) {
/**
* 成功获取到令牌
*/
return true;
}
/**
* 获取失败,直接响应“错误信息”
* 也可以通过抛出异常,通过全全局异常处理器响应客户端
*/
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.getWriter().write("服务器繁忙");
return false;
}
}
拦截器的配置
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.google.common.util.concurrent.RateLimiter;
import io.springboot.jwt.web.interceptor.RateLimiterInterceptor;
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* test接口,1秒钟生成1个令牌,也就是1秒中允许一个人访问
*/
registry.addInterceptor(new RateLimiterInterceptor(RateLimiter.create(1, 1, TimeUnit.SECONDS)))
.addPathPatterns("/test");
}
}
客户端演示限流效
有热门推荐👇
好险!一入职,就遇到 MySQL 这么大 Bug!差点背锅走人...
老司机带你使用 GitLab + Docker 自动部署 Spring Boot 应用
Springboot启动扩展点超详细总结,再也不怕面试官问了
知乎高赞:Linux!为何他一人就写出这么强的系统,中国却做不出来?
Spring Boot 中 @EnableXXX 注解的驱动逻辑
有热门推荐👇
好险!一入职,就遇到 MySQL 这么大 Bug!差点背锅走人...
老司机带你使用 GitLab + Docker 自动部署 Spring Boot 应用
Springboot启动扩展点超详细总结,再也不怕面试官问了
知乎高赞:Linux!为何他一人就写出这么强的系统,中国却做不出来?
Spring Boot 中 @EnableXXX 注解的驱动逻辑
干货分享:扫码关注下面的公众号获取面试资料+项目实战资料(电商/聚合支付)
点击阅读原文,获免费JVM+MySQL+设计模式+分布式+微服务完整面试资料
点击阅读原文,获免费JVM+MySQL+设计模式+分布式+微服务完整面试资料