guava限流
原创大约 4 分钟
漏桶限流
Nginx可以通过限流算法实现对后端服务的保护,避免因服务器承载压力太大而被冲垮。
其实Google Guava也可以实现限流(网络上称之为令牌桶,但其实Google Guava是将令牌桶和漏桶进行了混合,结合了这两种算法的优点)。
引入依赖。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
然后创建漏桶服务类。
package com.xiangwang.commons.service;
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
/**
* 实现漏桶Service
*
*/
@Service
public class LeakyBucketService {
// 限制每秒请求数量(漏桶的流速)
private static final RateLimiter rateLimiter = RateLimiter.create(1);
// 请求编号
private volatile int count = 1;
public boolean limit(HttpServletRequest request) throws InterruptedException {
System.out.print("请求编号 = " + count++ + " ");
// 休眠一秒,取决于机器性能
Thread.sleep(1000);
if (rateLimiter.tryAcquire()) {
System.out.println(">>>>>>>> 请求成功 >>>>>>>>");
return true;
}
System.out.println(">>>>>>>> 被限流了 >>>>>>>>");
return false;
}
}
创建控制器类。
package com.xiangwang.commons.controller;
import com.xiangwang.commons.service.LeakyBucketService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* 桶限流Controller
*
*/
@RestController
public class LimitBucketController {
@Resource
private LeakyBucketService leakyBucketService;
@GetMapping("/limit")
public String limit(HttpServletRequest request) throws InterruptedException {
leakyBucketService.limit(request);
return "SUCCESS";
}
}
为了模拟大流量并发访问,需要借助Apache Jmeter测试工具。
下载Apache Jmeter,解压至指定目录,然后执行如下命令。
> cd ${APACHE_JMETER_HOME}/bin
> ./jmeter
在Apache Jmeter中创建测试接口。
Test Plan
上单击鼠标右键,依次选择Add
-> Thread
-> Thread Group
。

在Thread Group
上单击鼠标右键,依次选择Add
-> Sampler
-> HTTP Request
。

HTTP Request
界面中填入如下信息,和之前的Controller
对应。

回到Test Group
界面,填入如下信息。

启动Springboot服务,点击Apache Jmeter上的Start
按钮,运行测试。

从测试结果可以看到,漏桶限流发挥了作用。

令牌桶限流
漏桶是为了平滑访问流量,但是如果需要允许一些突发流量呢?
Google Guava并没有实现,但可以通过令牌桶的思路来自己实现。
和漏桶相比,令牌桶其实有几个比较关键的不同。
需要一个桶来装令牌。
请求数量会出现突发。
每次消费令牌的数量固定。
创建令牌桶的服务类。
package com.xiangwang.commons.service;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
/**
* 令牌桶Service
*
*/
@Service
public class TokenBucketService extends HandlerInterceptorAdapter {
// 桶的容量,可用令牌总量
private final int total = 10;
// 消费速率
private final int rate = 3;
public boolean limit(HttpServletRequest request) throws InterruptedException {
// 休眠一秒,取决于机器性能
Thread.sleep(1000);
if (tryAcquire()) {
return true;
}
return false;
}
// 以固定速率消费请求,漏桶容量固定,每次用户请求都得放入桶中,桶满则拒绝请求或等待
private synchronized boolean tryAcquire() {
// 网络请求数量是随机的
int requests = (int) (Math.random() * 10) + 1;
System.out.print("消费速率 = " + rate + ",请求数量 = " + requests + "; ");
if (total < requests + rate) {
// 桶满则拒绝
System.out.println("剩余容量 = " + (total - (requests + rate)) + "; >>>>>>>> 被限流了 >>>>>>>>");
return false;
} else {
// 桶还未满
System.out.println("剩余容量 = " + (total - (requests + rate)) + "; >>>>>>>> 请求成功 >>>>>>>>");
return true;
}
}
}
// 当前水量,即来不及处理的缓冲量
//private int water = 0;
// 最后一次加水时间
//private long time = System.currentTimeMillis();
// // 以固定速率消费请求,漏桶容量固定,每次用户请求都得放入桶中,桶满则拒绝请求或等待
// private synchronized boolean tryAcquire() {
// // 当前时间
// long now = System.currentTimeMillis();
// // 当前水量是一个变动的量,由消费速率决定,反映出缓冲量也是变化的,不小于0
// water = Math.max(0, (int) (water - (now - time) * rate /1000));
// System.out.print("当前水量 = " + water + "; ");
// // 网络请求数量是随机的
// int requests = (int) (Math.random() * 10) + 1;
// System.out.print("请求数量 = " + requests + ",桶剩余量 = " + (total - water) + "; ");
// time = now;
// if (total - water < requests) {
// // 桶满则拒绝
// System.out.println(">>>>>>>> 被限流了 >>>>>>>>");
// return false;
// } else {
// // 桶还未满
// water += requests;
// System.out.println("剩余容量 = " + (total - water) + "; >>>>>>>> 请求成功 >>>>>>>>");
// return true;
// }
// }
修改之前的控制器类。
package com.xiangwang.commons.controller;
import com.xiangwang.commons.service.TokenBucketService;
import com.xiangwang.commons.service.LeakyBucketService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* 桶限流Controller
*
* @author xiangwang
*/
@RestController
public class LimitBucketController {
@Resource
private LeakyBucketService leakyBucketService;
@Resource
private TokenBucketService tokenBucketService;
@GetMapping("/limit")
public String limit(HttpServletRequest request) throws InterruptedException {
// leakyBucketService.limit(request);
tokenBucketService.limit(request);
return "SUCCESS";
}
}
修改Apache Jmeter的并发线程的数量。

再次启动Springboot服务,点击Apache Jmeter上的Start
按钮,运行测试后就可以看到结果。

在消费速率不变时,请求数量会有突发,有时可以请求成功,有时又会被限流,取决于令牌桶中是否还有可用的令牌。
感谢支持
更多内容,请移步《超级个体》。