• 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
         */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    令牌累计

    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
             */
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在上面的代码中,令牌桶中最多只能积累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
             */
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在平滑预热限流的情况下,令牌是不会积累的。
    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)

    参考

  • 相关阅读:
    Javascript中的模块化详解
    Ubuntu部署OpenStack踩坑指南:还要看系统版本?
    你对MongoDB和Mysql的差异性了解多少?
    服务器远程管理-Windows远程桌面协议实操
    Springboot整合Elasticsearch
    Oracle基本介绍与基本使用
    链上房产赛道项目 ESTATEX 研报:以 RWA 的方式释放房产市场的潜力
    结构冲突-架构真题(三十三)
    Nginx 同一端口 同时支持http与https 协议
    Kotlin Multiplatform稳定版本发布:加速跨平台开发的新里程碑
  • 原文地址:https://blog.csdn.net/wlddhj/article/details/132566434