RateLimiter
速率限制器,是
Google Guava提供的基于令牌桶算法的实现类。
对请求流量进行限制。不仅可以控制并发,而且可以准确的控制TPS(每秒钟请求数)。
应用场景:
一年一度的天猫双11限时抢购,用户同时点击抢购,
汹涌澎湃的流量可能导致服务宕机。如果系统扛不住怎么办?加机器?加机器不够怎么办?业务降级,系统限流。对抢购接口进行流量控制,降低单位时间内对系统的访问量,减少系统压力,保持系统的可用性。以系统不被压挂为第一要务。
令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
算法描述:
-
假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中(每秒会有r个令牌放入桶中);
-
假设桶中最多可以存放b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;
-
当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌(不同大小的数据包,消耗的令牌数量不一样),并且数据包被发送到网络;
-
如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外(n个字节,需要n个令牌。该数据包将被缓存或丢弃);
-
算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。对于在流量限制外的数据包可以以不同的方式处理:(1)它们可以被丢弃;(2)它们可以排放在队列中以便当令牌桶中累积了足够多的令牌时再传输;(3)它们可以继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。
Demo源码
/**
* 速率是每秒5个许可
*/
private final static RateLimiter rateLimiter = RateLimiter.create(5.0);
public static void main(String[] args) {
long a = System.currentTimeMillis();
System.out.println("启动");
ExecutorService service = Executors.newCachedThreadPool();
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Request("第" + (i + 1) + "次请求"));
}
//提交任务
submitTask(service, list);
long end = System.currentTimeMillis();
System.out.println("请求结束,耗时" + (end - a) + "ms");
//停止线程
service.shutdownNow();
}
public static void submitTask(ExecutorService exe, List<Runnable> tasks) {
for (Runnable task : tasks) {
// 从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)
boolean time = rateLimiter.tryAcquire(200, TimeUnit.MILLISECONDS);
exe.execute(task);
System.out.println("获取是否成功: " + time );
}
}
}
class Request implements Runnable {
private String name;
public Request(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
}
}
运行之后的结果:
获取是否成功: true
第1次请求
获取是否成功: true
第2次请求
获取是否成功: true
第3次请求
获取是否成功: true
第4次请求
获取是否成功: true
第5次请求
获取是否成功: true
第6次请求
获取是否成功: true
第7次请求
获取是否成功: false
第9次请求
获取是否成功: false
第8次请求
第10次请求
获取是否成功: true
请求结束,耗时1401ms
//再用没有限流的方法测试一遍 public static void submitTaskNoLimiter(ExecutorService exe, List<Runnable> tasks) { for (Runnable task : tasks) { exe.execute(task); } } } 启动 第1次请求 第2次请求 第5次请求 第4次请求 第3次请求 第7次请求 第8次请求 第9次请求 第10次请求 请求结束,耗时6ms 第6次请求
大家对以上结果进行比较一下,可以明显的看出限流的作用了吧~
参考文章链接: