com.google.common.util.concurrent.RateLimiter
谷歌下的限流工具,采用的是令牌桶算法
一些限流算法
1、令牌桶算法
原理:系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝。允许一定程度的突发流量,若桶内令牌个数满足,并发请求可以同时获取,主要用于需要平滑突发限流的场景。
2、漏桶算法
原理:一个固定容量的漏桶,按照常量固定速率流出水滴,如果桶是空的,则不需流出水滴;可以以任意速率流入水滴到漏桶,如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。即:请求可以任意速率进来,但是会以固定的速率被处理,不允许并发处理,主要适用需要平滑突发流入速率的场景。
3、计数器限流
原理:是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。
可以使用java.util.concurrent.atomic.AtomicInteger
来控制并发次数,如:
public class CountAtomicTest {
private static AtomicInteger count = new AtomicInteger(0);
public static void main() {
if (count.get() >= 5) {
System.out.println("请求用户过多,请稍后再试!");
} else {
count.incrementAndGet();
try {
//处理核心逻辑
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
count.decrementAndGet();
}
}
}
}
也可以通过使用信号量 java.util.concurrent.Semaphore
来控制并发执行次数,如:
public class CountSemaphoreTest {
private static Semaphore semphore = new Semaphore(5);
public static void main() {
if(semphore.getQueueLength()>50){
System.out.println("当前等待排队的任务数大于50,请稍候再试...");
}
try {
semphore.acquire();
// 处理核心逻辑
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semphore.release();
}
}
}
二者区别:atomicInteger 将超过计数控制的请求直接拒绝,semaphore 将超过计数控制的请求放阻塞队列,阻塞队列超过一定大小可以拒绝请求。
com.google.common.util.concurrent.RateLimiter
用法简述
实现过程主要应用令牌桶算法
1、单线程场景,如下:
public static void main(String[] args){
//1s只放5个令牌 即200ms投一个令牌
final RateLimiter rateLimiter = RateLimiter.create(5);
try {
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
for (int i = 0; i < 50; i++) {
long time1 = System.currentTimeMillis();
//尝试获取令牌,返回等待的时间(s为单位)
System.out.println(i + "尝试中...,需要等待:" + rateLimiter.acquire() * 1000);
System.out.println(i + "已获得令牌,耗时:" + (System.currentTimeMillis() - time1));
try {
Thread.sleep(10);
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println("所有已完成");
}
运行结果如下:
0尝试中...,需要等待:0.0
0已获得令牌,耗时:0
1尝试中...,需要等待:0.0
1已获得令牌,耗时:0
2尝试中...,需要等待:0.0
2已获得令牌,耗时:0
3尝试中...,需要等待:54.418
3已获得令牌,耗时:57
4尝试中...,需要等待:184.753
4已获得令牌,耗时:186
5尝试中...,需要等待:186.215
5已获得令牌,耗时:185
6尝试中...,需要等待:190.492
6已获得令牌,耗时:190
7尝试中...,需要等待:190.467
7已获得令牌,耗时:190
8尝试中...,需要等待:190.593
8已获得令牌,耗时:191
9尝试中...,需要等待:189.596
9已获得令牌,耗时:190
10尝试中...,需要等待:189.513
10已获得令牌,耗时:190
11尝试中...,需要等待:189.646
11已获得令牌,耗时:190
12尝试中...,需要等待:189.552
12已获得令牌,耗时:190
13尝试中...,需要等待:189.494
13已获得令牌,耗时:189
14尝试中...,需要等待:190.537
14已获得令牌,耗时:191
15尝试中...,需要等待:189.649
15已获得令牌,耗时:189
16尝试中...,需要等待:189.445
16已获得令牌,耗时:189
17尝试中...,需要等待:188.459
17已获得令牌,耗时:190
18尝试中...,需要等待:189.344
18已获得令牌,耗时:189
19尝试中...,需要等待:190.525
19已获得令牌,耗时:191
20尝试中...,需要等待:189.38
20已获得令牌,耗时:189
...
...
刚开始程序启动初始化 rateLimiter 开始每隔 200ms投令牌,所以前几个基本很快拿到了令牌,再往后基本是恒定速率拿到令牌。
2、并发场景如下:
public static void main(String[] args){
//用countDownLatch模拟并发
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch runLatch = new CountDownLatch(10);
ExecutorService exe = Executors.newFixedThreadPool(10);
//1s只放5个令牌
final RateLimiter rateLimiter = RateLimiter.create(5);
for (int i = 0; i < 10; i++) {
final int j = i;
exe.submit(() -> {
try {
//等待统一发号施令
startLatch.await();
long time1 = System.currentTimeMillis();
System.out.println(j + "尝试中...,需要等待:" + rateLimiter.acquire() * 1000);
System.out.println(j + "已获得令牌,耗时:" + (System.currentTimeMillis() - time1));
} catch (Exception e) {
e.printStackTrace();
} finally {
runLatch.countDown();
}
});
}
try {
//等待700ms 使令牌桶中先有几个令牌
Thread.sleep(700);
System.out.println("即将发号施令");
//发号施令
startLatch.countDown();
//等待所有完成
runLatch.await();
System.out.println("所有已完成");
}catch (Exception e){
e.printStackTrace();
}
}
运行结果如下:
即将发号施令
0尝试中...,需要等待:0.0
0已获得令牌,耗时:0
4尝试中...,需要等待:0.0
4已获得令牌,耗时:0
8尝试中...,需要等待:0.0
8已获得令牌,耗时:0
1尝试中...,需要等待:0.0
1已获得令牌,耗时:0
5尝试中...,需要等待:23.738
5已获得令牌,耗时:26
9尝试中...,需要等待:222.748
9已获得令牌,耗时:224
2尝试中...,需要等待:402.048
2已获得令牌,耗时:401
6尝试中...,需要等待:600.35
6已获得令牌,耗时:599
3尝试中...,需要等待:800.318
3已获得令牌,耗时:800
7尝试中...,需要等待:1000.302
7已获得令牌,耗时:998
所有已完成
可以看到 0,4,8,1基本同时拿到令牌,后面的基本等待 200ms 才能取到令牌继续执行。只要令牌桶中有足够数量的令牌,可以允许一定程度的并发,比如秒杀场景的限流。
了解RateLimiter 的更多方法:http://ifeve.com/guava-ratelimiter/