ReateLimiter 官方Demo
ReateLimiter 相关文档中文版
《亿级流量网站架构核心技术》张开涛 限流详解
令牌桶限流
上图为令牌桶的原理图,根据原理图能够得知:
令牌桶的组成部分
1、令牌 - 生成
2、令牌 - 存放
3、令牌 - 获取
令牌桶的原理
1、令牌生成器:根据设定的令牌生成策略生成令牌,并将令牌存放至令牌存放地(令牌桶)中。
2、令牌获取器:用户请求到达,尝试从令牌桶中获取令牌,如果获取到令牌则放行,如果获取不到则根据限流规则进行友好提示。
RateLimiter:令牌桶实现工具类
RateLimiter 是 Google 开源工具包 Guava 中针对 令牌桶算法的实现工具类。
RateLimiter 简单使用案例
public class RateLimiterDemo {
public static void main(String[] args) throws IOException {
// 令牌获取
// 1-阻塞获取
block();
// 2-非阻塞获取
non_block();
System.in.read();
}
public static void non_block() {
// 令牌生成 并放入令牌桶中
RateLimiter rateLimiter = RateLimiter.create(1.0);
long startTime = System.currentTimeMillis();
IntStream.rangeClosed(0,6).forEach(
item ->{
boolean falg = rateLimiter.tryAcquire(1, TimeUnit.MILLISECONDS);
System.err.println("成功与否:" + falg);
System.err.println("消耗时间:" + (startTime - System.currentTimeMillis()));
}
);
}
public static void block() {
// 令牌生成 并放入令牌桶中
RateLimiter rateLimiter = RateLimiter.create(1.0);
long startTime = System.currentTimeMillis();
IntStream.rangeClosed(0,6).forEach(
item ->{
double waiteTime = rateLimiter.acquire(1);
System.err.println("等待时间:" + waiteTime);
System.err.println("消耗时间:" + (startTime - System.currentTimeMillis()));
}
);
}
}
在演示了 RateLimter 的简单使用之后,我们根据 令牌桶 算法的三个组成部分(令牌生成、令牌存放、令牌获取)对 RateLimter 简单进行源码剖析
RateLimter 原理
令牌桶算法的理解很简单。令牌生成,令牌获取,令牌存储,这三部分组成了令牌桶限流算法。
对于令牌桶的实现,存在两种实现方式:
- 1、定时线程生成令牌方式
- 2、触发更新令牌方式
定时线程生成令牌方式实现思路:
- 开辟一个空间用来存放令牌,
- 提供一个线程用来产生令牌并放入令牌存储空间,
- 提供一个方法,用来尝试获取空间中的令牌。
下面重点来讲讲 RateLimter 通过触发更新的方式 如何实现令牌通算法。
以 RateLimter 的子类 SmoothBursty 进行原理分析
图中重要信息如下:
-
1、nowMicros : 记录代码运行时长
-
2、nextFreeTicketMicros : nextFreeTicketMicros = 每个令牌生成耗时 * 已被获取的令牌数
SmoothBursty 和 SmoothWarmingUp 类似,SmoothBursty 生成令牌的速度是恒定的,SmoothWarmingUp 在一段时间内的速度是动态的。
在 SmoothBursty 中:nextFreeTicketMicros = stableIntervalMicros * 已被获取的令牌数 。stableIntervalMicros 为:每个令牌生成平均耗时。
RateLimter 总体的实现方式:将令牌的生成换算成时间,通过时间轴进行划分。以程序运行作为起始,定义两个重要时间 nowMicros nextFreeTicketMicros。
nowMicros 指向当前程序运行时间点,从而根据令牌生成速率得到到目前为止,根据速率令牌桶中应该存放有的令牌数。
nextFreeTicketMicros 表示:已被使用令牌数 换算之后的时间长度。
RateLimter 使用触发更新的方式进行令牌更新,也就是当调用 RateLimter#acquire 进行令牌获取时才会进行令牌桶的相关令牌数的计算更新。而更新的比对条件:nowMicros > nextFreeTicketMicros。
而该更新条件也造就了 RateLimter 所谓的预消费能力。假设两次请求间隔1s,第一次请求就能够预消费 1s 生成的令牌数,因为在第二次请求抵达的时候所被预消费的1s的令牌数因为时间间隔被补齐,第二次请求也无需等待。
RateLimter 源码解析
以令牌的初始化,和令牌消费 作为入口进行分析。
令牌初始化(解析 SmoothBursty)
在 RateLimiter 中有4个重载的令牌生成器构造方法 RateLimiter#create
我们再来看看源码,比对一下这 4 个 RateLimiter#create 方法
通过源码的比对,能够得到,这四个令牌生成的方法,最终构造的 RateLimter 对象,分为两种。
- SmoothWarmingUp
平滑预热限流 - SmoothBursty
平滑突发限流
先来看看 SmoothWarmingUp 和 SmoothBursty 与 RateLimter 的关系
通过查看类结构关系,能够直观的知道,RateLimter 有一个直属的子类 SmoothRteLimiter ,而 SmoothWarmingUp 和 SmoothBursty 继承自 SmoothRteLimiter (同时也是SmoothRteLimiter 内部类)。
在对 SmoothWarmingUp 和 SmoothBursty 这两个令牌生成类进行解析前,先来简单看看他们的父类 RateLimiter 和 SmoothRteLimiter
RateLimiter
RateLimiter 相关属性定义
- private final SleepingStopwatch stopwatch;
guava一个时钟,提供两种功能:获取代码运行时长 和 提供 sleep 功能。 - private volatile Object mutexDoNotUseDirectly;
该对象是一个锁对象,用来作为相关操作的锁对象。
而获取锁的操作方法为 RateLimiter#mutex
再来看看 相关的方法调用
-
RateLimiter#create
令牌生成构造方法 -
RateLimiter#acquire
阻塞方式,获取令牌 -
RateLimiter#tryAcquire
非阻塞方式,获取令牌
RateLimter 提供了令牌桶相关的操作方法,具体的实现由其子类完成。
SmoothRateLimiter
SmoothRateLimiter 相关属性
- double storedPermits;
当前存储令牌数 - double maxPermits;
存储最大令牌数 - double stableIntervalMicros;
每个令牌的稳定生成速率。例