Google Guava之RateLimiter核心源码解读(中)

本文详细解读了Google Guava的RateLimiter,重点分析了创建RateLimiter实例和获取许可的核心源码,探讨了其令牌桶实现和突发流量支持。通过示例和类图,展示了RateLimiter如何维持稳定速率,以及如何通过SmoothBursty和SmoothWarmingUp实现限速功能。RateLimiter通过存储未使用的许可数提高资源利用率,但不保证请求的公平性。
摘要由CSDN通过智能技术生成

RateLimiter是Google Guava框架的一个限速器,通常用于控制对某个资源的访问速率。

限速常见的有两种实现方式,一种是令牌桶,另一种是漏桶。

RateLimiter选择了令牌桶作为其底层实现,按照固定速率投放令牌,同时支持突发流量。

在上一篇中,我们先简单解读了Ticker和Stopwatch,它们是RateLimiter底层时间计算的基础。

传送门:《Google Guava之RateLimiter核心源码解读(上)

这一篇,我们将要解读的是创建RateLimiter实例和获取许可的核心代码,涉及到RateLimiter、SmoothRateLimiter和SmoothBursty。

代码仍然是基于Guava 23.0版本。

在进行源码分析之前,先来看一个简单的例子,找点感觉。

示例

@Test
public void testFirst() {
   
	RateLimiter rateLimiter = RateLimiter.create(1);
	while (true) {
   
		System.out.println(rateLimiter.acquire(1));
	}
}
/*
执行结果:
0.0
0.993779
0.976652
0.967317
0.9815
0.999405
0.997789
...
*/

在这段代码中,首先创建了一个RateLimiter实例,然后通过while循环不断从限速器中获取许可。

其中,RateLimiter.create(1)表示创建一个RateLimiter实例,每秒的许可数为1。也就是说,如果想从限速器中获得许可,那么每秒最多只能获取1个。

rateLimiter.acquire(1)表示从限速器中获取1个许可,该方法会阻塞直到获得了指定的许可数。

执行程序输出的结果,表示每次获得许可所等待的时间,单位为秒。显然,每获得1个许可需要花费约1秒的时间。这个1秒,就是在创建RateLimiter时,create方法传入的1,即限速器每秒可发放的许可数。

RateLimiter的核心设计思想

RateLimiter的一个主要特性,是能够提供稳定(或固定)的速率。对于这一点,可以通过在初始化时,传入一个期望的速率值。然后根据这个值,计算出一个具体的限制时间,使得线程在获取许可时进行相应的等待。

那么,具体要如何来实现对速率的控制呢?

最简单的方法,就是保留最后一次发放许可给线程请求的时间戳,并且确保在1/QPS秒之后才允许下一次请求许可通过。

其中,QPS指每秒的最大许可数,即每秒最多允许发放多少个许可。而1/QPS,表示每发放1个许可的间隔时间。

例如,对于QPS为5的速率(即每秒发送5个许可),我们只要确保距最后一次准许请求通过的时间差达到200ms,才发放下一个许可,即可满足预期的速率要求。

如果一个新的请求到来了,但距最后一个请求被批准的时间仅过了100ms,那么本次请求需要再等待100ms。

因此,以这样的速率,发送15个许可,总共需要3秒(15除以5)。

如果一个请求速率较高的任务到达一个比较空闲的RateLimiter,那么该任务将立即被授予指定的许可。然而,下一个请求将会受到额外的限制,因为前一个请求预取了一部分许可,需要由本次请求来承担额外的等待时间。

这其实就是RateLimiter支持突发访问流量的体现,但这同时也说明了RateLimiter并不提供多个请求间的公平保证。(想想先来的请求可能不需要等待,而下一个却要为前一个请求进行额外的等待)

基于以上的设计,如果RateLimiter长时间未使用,或者比较空闲的时候,那么空闲期间的许可我们是没有使用到的,这样将会被造成浪费,可能导到资源利用率低等问题。

为了充分利用资源,将空闲期间的许可给利用起来,需要有一个地方来记录空闲期间的许可数。在RateLimiter中,使用storedPermits来存储未被使用的许可数。

默认的RateLimiter配置可以保存最多一秒钟的未使用许可。

RateLimiter类图

RateLimiter的类关系图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SDBwuOe4-1601827666832)(https://binarylife.icu/wp-content/uploads/2020/09/RateLimiter-Class-Diagram.png)]

RateLimiter和SmoothRateLimiter都是抽象类,SmoothRateLimiter继承了RateLimiter。

SmoothBursty和SmoothWarmingUp都继承SmoothRateLimiter,是RateLimiter的最终实现类。

同时,SmoothBursty和SmoothWarmingUp直接在SmoothRateLimiter内部定义,都是静态的内部类。

因此,在创建RateLimiter的具体实例时,实际上是在内部创建SmoothBursty或SmoothWarmingUp实例。

核心源码解读

创建RateLimiter实例


public abstract class RateLimiter {
   
  /**
   * 创建一个RateLimiter实例,并设置每秒的许可数.
   */
  public static RateLimiter create(double permitsPerSecond) {
   
    return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
  }
  
  /**
   * 创建一个默认实现为SmoothBursty的RateLimiter实例.
   * 
   * @param permitsPerSecond 每秒许可数
   * @param stopwatch 支持sleep的秒表
   * @return
   */
  static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
   
	// 创建一个RateLimiter实例,默认实现为SmoothBursty. 突发时间为1s
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
    rateLimiter.setRate(permitsPerSecond); // 设置速率,即每秒的许可数
    return rateLimiter;
  }
}

其中,SleepingStopwatch.createFromSystemTimer()表示创建一个支持sleep方法的秒表。SleepingStopwatch有两个方法,一个是获取从启动限速器到当前的时间,另一个是执行不可中断的睡眠。

public static final SleepingStopwatch createFromSystemTimer() {
   
  return new SleepingStopwatch() {
   
	// 初始化秒表
    final Stopwatch stopwatch = Stopwatch.createStarted();

    /**
     * 从启动到现在经过的微秒数。相当于ticker.read() - startTick,即"当前纳秒 - 启动时的纳秒",然后转换为微秒。
     */
    @Override
    protected long readMicros() {
   
      return stopwatch.elapsed(MICROSECONDS);
    }

    /**
     * 不可中断的进行sleep,时间为微秒。<br>
     * 实际上,如果遇到中断,会先忽略中断异常,继续sleep,直到等待了指定时长后才结束。
     */
    @Override
    protected void sleepMicrosUninterruptibly(long micros) {
   
      if (micros 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值