Guava RateLimiter限流

令牌桶算法

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌;漏桶限制的是常量流出速率,即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2,从而平滑突发流入速率;
  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流出速率;

Guava RateLimiter

Guava的 RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

平滑突发限流

使用 RateLimiter的静态方法创建一个限流器,设置每秒放置的令牌数为5个。
返回的RateLimiter对象可以保证1秒内不会给超过5个令牌,并且以固定速率进行放置,达到平滑输出的效果。

public void testSmoothBursty() {
    
    RateLimiter r = RateLimiter.create(5);
    while (true) {
        System.out.println("get 1 tokens: " + r.acquire() + "s");
    }
    /**
     * output: 基本上都是0.2s执行一次,符合一秒发放5个令牌的设定。
     * get 1 tokens: 0.0s
     * get 1 tokens: 0.182014s
     * get 1 tokens: 0.188464s
     * get 1 tokens: 0.198072s
     * get 1 tokens: 0.196048s
     * get 1 tokens: 0.197538s
     * get 1 tokens: 0.196049s
     */
}

令牌累计

Guava RateLimiter的令牌积累数量是根据设定的速率和时间间隔来计算的。具体计算方式如下:

  1. 首先,RateLimiter会根据设定的速率(每秒生成的令牌数量)计算出令牌生成的时间间隔。
    例如,如果设定的速率是2个令牌/秒,那么每个令牌生成的时间间隔是0.5秒(1秒/2个令牌)。
  2. 当RateLimiter开始工作时,它会记录当前时间,并将令牌桶中的令牌数量初始化为0。
  3. 当一个请求到达时,RateLimiter会计算当前时间与上一次记录时间之间的时间间隔,并根据设定的速率和时间间隔计算出应该生成的令牌数量。
  4. 如果计算出的令牌数量小于等于令牌桶中的剩余令牌数量,请求将被允许通过,并且令牌桶中的令牌数量减少。
  5. 如果计算出的令牌数量大于令牌桶中的剩余令牌数量,请求将被限制或延迟处理,直到令牌桶中有足够的令牌可用。
    总结来说,Guava RateLimiter根据设定的速率和时间间隔来计算应该生成的令牌数量,并根据令牌桶中的剩余令牌数量来决定请求是否被允许通过。
public void testSmoothBursty2() {
    // RateLimiter使用令牌桶算法,会进行令牌的累积,如果获取令牌的频率比较低,则不会导致等待,直接获取令牌。
    RateLimiter r = RateLimiter.create(2);
    while (true) {
        try {
            Thread.sleep(3000);
        } catch (Exception e) {
        }
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("end");
        /**
         * output:
         get 1 tokens: 0.0s
         get 1 tokens: 0.0s
         get 1 tokens: 0.0s
         get 1 tokens: 0.499147s
         end
         get 1 tokens: 0.0s
         get 1 tokens: 0.0s
         get 1 tokens: 0.0s
         get 1 tokens: 0.499904s
         */
    }
}

在上面的代码中,令牌桶中最多只能积累2个令牌是由于创建RateLimiter时指定的速率为2。RateLimiter.create(2)表示每秒生成2个令牌。令牌桶的大小取决于速率和时间间隔之间的关系。在这种情况下,速率为2个令牌/秒,意味着每秒生成2个令牌放入令牌桶中。

根据令牌桶算法,令牌桶的大小可以理解为令牌桶的容量,即最多可以容纳的令牌数量。在这里,令牌桶的容量为2,也就是最多可以积累2个令牌。

如果在某个时间点上,令牌桶中已经有2个令牌,而没有请求来消耗这些令牌,那么令牌桶中不会继续积累更多的令牌。新的令牌只有在之前的令牌被消耗后才会生成并放入令牌桶中。

因此,根据上述代码和速率设置,令牌桶中最多只能积累2个令牌。

平滑预热限流

RateLimiter的 SmoothWarmingUp是带有预热期的平滑限流,它启动后会有一段预热期,逐步将分发频率提升到配置的速率。 比如下面代码中的例子,创建一个平均分发令牌速率为2,预热期为3分钟。由于设置了预热时间是3秒,令牌桶一开始并不会0.5秒发一个令牌,而是形成一个平滑线性下降的坡度,频率越来越高,在3秒钟之内达到原本设置的频率,以后就以固定的频率输出。这种功能适合系统刚启动需要一点时间来“热身”的场景。

public void testSmoothwarmingUp() {
    RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS);
    while (true)
    {
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("get 1 tokens: " + r.acquire(1) + "s");
        System.out.println("end");
        /**
         * output:
         * get 1 tokens: 0.0s
         * get 1 tokens: 1.329289s
         * get 1 tokens: 0.994375s
         * get 1 tokens: 0.662888s  上边三次获取的时间相加正好为3秒
         * end
         * get 1 tokens: 0.49764s  正常速率0.5秒一个令牌
         * get 1 tokens: 0.497828s
         * get 1 tokens: 0.49449s
         * get 1 tokens: 0.497522s
         */
    }
}

在平滑预热限流的情况下,令牌是不会积累的。
RateLimiter.create(2, 3, TimeUnit.SECONDS);

  • 参数1:每秒产生2个令牌
  • 参数2/3:在前3秒内,产生3个令牌,且从慢到快
  • 如果RateLimiter.create(2, 4, TimeUnit.SECONDS); 代表在前4秒内,产生4个令牌,且从慢到快

核心函数说明

函数 说明 示例
public static RateLimiter create(double permitsPerSecond) 每秒产生permitsPerSecond个令牌 RateLimiter.create(2)
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 每秒产生permitsPerSecond个令牌,在前warmupPeriod单位时间内,产生warmupPeriod个令牌,从慢到快 RateLimiter.create(2, 3, TimeUnit.SECONDS)
public double acquire(int permits) 取得指定数量的令牌,并返回等待时间 limiter.acquire(1)
public boolean tryAcquire(int permits) 尝试取得指定数量的令牌,返回成功或失败 limiter.tryAcquire(1)
public boolean tryAcquire(Duration timeout) 尝试取得1个令牌,并等待指定的时间,返回成功或失败 limiter.tryAcquire(Duration.ofSeconds(1))
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) 尝试取得指定数量的令牌,并等待指定的时间,返回成功或失败 limiter.tryAcquire(2, 10, TimeUnit.SECONDS)

参考

  • https://zhuanlan.zhihu.com/p/60979444

你可能感兴趣的:(其他,guava,算法,限流)