有一个固定大小的水桶,在水桶的水满之前,水龙头按照一定的频率恒定往里面滴水。水满之后就不滴了。
每次处理业务请求之前,都要先尝试从水桶里取出一滴水,才能处理业务。如果取不到,就不处理业务。
如此以来,桶的大小固定,水龙头滴水频率恒定,那么总的访问量也就恒定。从而也就保证了数据接口的访问流量,从而达到限流的目的。

guava是来自谷歌的Java核心类库。包含了大量Java工具类、集合类型、不可变集合、并发、I/O、hashing、缓存、原型、字符串等的通用功能。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
创建一个限速器,预热1秒,产生5个令牌
permitsPerSecond:每秒产生多少个令牌
warmupPeriod:预热时间
unit:预热时间warmupPeriod的单位
RateLimiter rateLimiter = RateLimiter.create(5, 1, TimeUnit.SECONDS);
尝试获取1个令牌,如果没有,会阻塞当前线程。直到获取成功返回。
返回值:阻塞的秒数。
double waitSeconds = rateLimiter.acquire();
尝试获取1个令牌,不会阻塞当前线程。
返回值:是否获取成功。
boolean success = rateLimiter.tryAcquire();
以上就是主要基础代码,创建限速器(生成水桶),(阻塞/不阻塞)获取令牌。
阻塞线程获取令牌
默认获取一个令牌。
public double acquire();
获取令牌数量手动传参,参数:permits。
public double acquire(int permits);
非阻塞线程获取令牌
参数说明如下
permits:获取令牌数量
timeout:等待时长
unit:时间单位
默认获取1个令牌,等待0秒,时间单位为微秒,其余同理
public boolean tryAcquire();
public boolean tryAcquire(int permits);
public boolean tryAcquire(Duration timeout);
public boolean tryAcquire(long timeout, TimeUnit unit);
public boolean tryAcquire(int permits, Duration timeout);
public boolean tryAcquire(int permits, long timeout, TimeUnit unit);
单接口测试如下
package com;
import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.TimeUnit;
public class Test {
private final RateLimiter rateLimiter;
public Test(RateLimiter rateLimiter) {
this.rateLimiter = rateLimiter;
}
public static void main(String[] args) {
Test test = new Test(RateLimiter.create(1, 1, TimeUnit.SECONDS));
for(int i = 0; i < 10; i++){
rateLimiter(test.rateLimiter);
}
}
private static void rateLimiter(RateLimiter rateLimiter){
if(rateLimiter.tryAcquire()) {
doBusiness();
return;
}
System.out.println("服务器忙,请稍后重试!");
}
private static void doBusiness(){
System.out.println("可以通行,处理业务");
}
}
运行结果

单接口可以处理之后,那么就需要用到一批接口上,做成配置的形式。
由于限流的本质就是对请求的拦截,那么自然而然就能想到使用拦截器处理。
限流处理的本质,类似于java中的切片,所以除了拦截器,也可以想到的是AOP,可以采用监听,使用注解等等方式。
那么就用拦截器先搞一搞吧,建一个拦截器如下
package com;
import com.alibaba.fastjson.JSON;
import com.Result;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
/**
* 令牌桶算法拦截器
*/
@Component
@Slf4j
public class RateLimiterInterceptor extends HandlerInterceptorAdapter {
private final RateLimiter rateLimiter;
/**
* 通过构造函数默认初始化限速器,创建一个限速器,预热1秒,产生10个令牌
* permitsPerSecond:每秒产生多少个令牌
* warmupPeriod:预热时间
* unit:预热时间warmupPeriod的单位
*/
public RateLimiterInterceptor() {
super();
this.rateLimiter = RateLimiter.create(5, 1, TimeUnit.SECONDS);
}
/**
* 通过构造函数手动初始化限速器
*/
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.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().write(JSON.toJSONString(Result.error(400, "服务器繁忙,请稍后重试!")));
return false;
}
}
在WebMvcConfiguration中配置
package com.config;
@Slf4j
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private RateLimiterInterceptor rateLimiterInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimiterInterceptor).addPathPatterns("/rate/limiter/**");
}
}
测试Controller接口
注意:路径必须是 /rate/limiter
@Controller
@RequestMapping("/rate/limiter")
public class Test Controller {
@Resource
private ITest test;
@RequestMapping("/api")
@ResponseBody
public Result doBusiness(@RequestBody String json) throws Exception {
return test.doBusiness(json);
}
}
效果同上。
加入每秒产生1个令牌,那在1秒内,只有第一次调用接口可以正常处理,其余接口均提示系统繁忙。
OK,整理到这吧!
如有不正确之处,还望指正!书写不易,觉得有帮助就点个赞吧!☺☺☺