Guava RateLimiter限流

GuavaRateLimiter:令牌桶算法详解及平滑限流实现,
本文介绍了令牌桶算法及其在GuavaRateLimiter中的应用,包括平滑突发限流和平滑预热限流的概念,以及如何通过RateLimiterAPI进行令牌获取和控制流量。

令牌桶算法

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
  • 令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿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)

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顽石九变

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值