高性能限流器Guava RateLimiter
我们来看看 Guava RateLimiter 是如何解决高并发场景下的限流问题的。
限流怎么理解呢?(我们创建一个流速为2个请求/秒的限流器)
直观地看,2个请求/秒就是每秒最多允许两个请求通过限流器。
在Guava中,2个请求/秒==一个请求500毫秒
首先我们看看RateLimiter的使用
//限流器流速:2个请求/秒
RateLimiter limiter =
RateLimiter.create(2.0);
//执行任务的线程池
ExecutorService es = Executors
.newFixedThreadPool(1);
//记录上一次执行时间
prev = System.nanoTime();
//测试执行20次
for (int i=0; i<20; i++){
//限流器限流
limiter.acquire();
//提交任务异步执行
es.execute(()->{
long cur=System.nanoTime();
//打印时间间隔:毫秒
System.out.println(
(cur-prev)/1000_000);
prev = cur;
});
}
输出结果:
...
500
499
499
500
499
经典限流算法:令牌桶算法
Guava采用的就是令牌桶算法,核心就是通过限流器,必须拿到令牌。
令牌桶算法详情描述:
- 令牌以固定的速率添加到令牌桶中,假设限流的速率是 r/ 秒,则令牌每 1/r 秒会添加一个;
- 假设令牌桶的容量是 b ,如果令牌桶已满,则新的令牌会被丢弃;
- 请求能够通过限流器的前提是令牌桶中有令牌。
如果让我用java实现,我可能会想到生产设-消费者模式,一个生产者线程定时向阻塞队列中添加令牌,而试图通过限流器的线程作为消费者,只有从阻塞队列中取到令牌,才能通过限流器。
但是在高并发场景下,定时器的精度误差会特别大,同时定时器会创建调度线程,对系统的影响较大。
Guava实现令牌桶算法关键是记录一下令牌的发放时间。
下面通过图解来详细描述一下。
假设令牌桶的容量为 b=1,限流速率 r = 1 个请求 / 秒,
下图当前下一令牌产生时间在第三秒,t1线程在第二秒产生。
t1线程占用第三秒的令牌,下一令牌+1秒,到第四秒。
此时在第2秒t2线程产生,t2占用4秒的下一令牌。
然后下一令牌继续+1秒,到第5秒。
前面都是讲解的线程请求在令牌前,下面说一下请求在令牌后面的情况,
当前下一令牌产生时间在5秒,第7秒t3线程产生,拿到5秒的令牌,
因为限流每秒产生一个,所以下一令牌产生时间为当前时间+1秒(间隔时间)=8秒
以上就是令牌的桶的简单实例情况。