写点什么

限流篇,欣赏 guava 的 RateLimiter

用户头像
下雨喽
关注
发布于: 2021 年 06 月 07 日

为什么令牌桶实现不用生产-消费模式

将令牌桶的原理翻译成代码,一个生产令牌的线程 sleep 几秒,然后放到 ConcurrentQueue 一个令牌,业务线程取令牌的时候去 offer,为什么不这样做?

原因是限流发生在系统承受的极限上,这种时候限流的代码越稳定、越简单,效果就越好,生产令牌的线程可能跑飞、jvm 可能有 bug、可能不会被调度,总不能再加一个线程组吧,那怎么办?

假设每秒产生 1 个令牌,产生 n 的令牌,就需要 n 秒,只要有时间差,用时间差 / 速率,就能得到有多少令牌,那么时间差怎么来的?

当线程请求的这一刻,这个时间是确定的,记为 now,有令牌出现的时间,记为 last,(now-last) / rate 就等于当前有多少令牌了

为什么 RateLimiter 类中没有变量、计算

看一下 Ratelimit 类的方法,create 初始化、doSetRate 设置速率、acquire 获取令牌... 以 acquire 为例,我们要干嘛呢,如果有令牌就返回,如果没有就等待,等待时间可以计算出来

那么逻辑主要在获取等待时间里,进去是加锁、返回等待时间,再进去 max(time - now, 0),后面是一个 abstract 方法,这个方法就是实现类自己的实现策略了

也就是说,外面看到的 RateLimiter,只有做了什么,而没有怎么做,涉及的概念:速率、等待时间、令牌,简单点看就是上文中计算公式的主要变量,但这个变量怎么来的、怎么计算、怎么存储,Ratelimit 类里面是没有的

这么做依据什么思想呢,开闭?面向接口?不如多看点代码、多想想,念叨名词是没用的

为什么叫 SmoothRateLimiter 平滑实现类

到了实现类 SmoothRateLimiter,平滑限流,那意思是线性的呗,实现的变量都在了,当前存储的令牌数 storedPermits,最大存储的令牌数 maxPermits,单位生产间隔时间 stableIntervalMicros,下次令牌出现时间 nextFreeTicketMicros

下面是获取等待时间的注释版代码,看一眼就可以

  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {    // 若now大于nextFreeTicketMicros,计算该段时间内,应该已经生成多少令牌,更新数据    resync(nowMicros);    long returnValue = nextFreeTicketMicros;    // 能分配的令牌数    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);    // 要再生产的令牌数    double freshPermits = requiredPermits - storedPermitsToSpend;    // 等待时间 = 再生产的令牌数 * 单位生产时间 + 动态调整时间    long waitMicros =        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)            + (long) (freshPermits * stableIntervalMicros);    // 下一次请求令牌的时间 += 等待微妙数    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);    // 更新存储令牌数    this.storedPermits -= storedPermitsToSpend;    return returnValue;  }
复制代码

我们可以看到,有两种实现类,一个是 SmoothBursty,平滑稳定版,另一个是 SmoothWarmingUp,平滑慢热版,区别从这个注释可以看出来,主要的实现方法是三个,coolDownIntervalMicros 单位冷却时间能产生的令牌数,storedPermitsToWaitTime 动态调整等待时间,doSetRate 设置生产速率

   * <pre>   *          ^ throttling   *          |   *    cold  +                  /   * interval |                 /.   *          |                / .   *          |               /  .   ← "warmup period" is the area of the trapezoid between   *          |              /   .     thresholdPermits and maxPermits   *          |             /    .   *          |            /     .   *          |           /      .   *   stable +----------/  WARM .   * interval |          .   UP  .   *          |          . PERIOD.   *          |          .       .   *        0 +----------+-------+--------------→ storedPermits   *          0 thresholdPermits maxPermits   * </pre>
复制代码

有没有感觉到,不论是类、方法、变量命名,方法分层、注释都挺明了的,想想你写的 IF {IF {FOR}} ELSE{ WHILE{}},你可长点心吧

为什么 SmoothBursty、SmoothWarmingUp 是内部静态类

没有明显的结论,把 SmoothRateLimiter 当成一个轮廓,平滑的实现都在这里面?但是也没别的实现了呀,有必要这么做吗

结束语,欢迎各位批评

发布于: 2021 年 06 月 07 日阅读数: 22
用户头像

下雨喽

关注

海风很腥,海水很苦 2021.06.02 加入

欢迎批评

评论

发布
暂无评论
限流篇,欣赏guava的RateLimiter