Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >源码分析 RateLimiter SmoothBursty 实现原理(文末附流程图)

源码分析 RateLimiter SmoothBursty 实现原理(文末附流程图)

作者头像
丁威
发布于 2020-03-31 04:05:18
发布于 2020-03-31 04:05:18
1.5K00
代码可运行
举报
文章被收录于专栏:中间件兴趣圈中间件兴趣圈
运行总次数:0
代码可运行

上篇详细介绍了Sentinel FlowSlot 限流实现原理(文末附流程图与总结)的限流实现机制,但主要介绍的策略限流的快速失败机制,在Sentinel 中除了快速失败,还提供了匀速排队,预热等限流策略,但我发现 Sentinel 的匀速排队、预热机制是基于 guava 的 RateLimiter,为了更加彻底的理解 Sentienl 限流相关的内容,从本文开始先来学习一下 RateLimiter 的相关实现原理。

温馨提示:文章的末尾会总结 SmoothBursty 的核心流程图与实现原理,本文将展示笔者是如何一步一步揭晓其实现原理的方法。

1、RateLimiter 类设计图


  • RateLimiter 限流抽象类,定义限流器的基本接口。
  • SmoothRateLimiter 平滑限流实现器,也是一个抽象类。
  • SmoothWarmingUp 自带预热机制的限流器实现类型。
  • SmoothBursty 适应于突发流量的限流器。

上述类这些属性,在讲解 SmoothBursty、SmoothWarmingUp 时再详细介绍。

温馨提示:可以看看这些类上的注释,先初步了解其设计思想。

2、寻找入口


我们首先从 guava 的测试用例中尝试寻找一下 RateLimiter 的测试类,测试代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void testSimple() {
    RateLimiter limiter = RateLimiter.create(stopwatch, 5.0);
    limiter.acquire(); // R0.00, since it's the first request
    limiter.acquire(); // R0.20
    limiter.acquire(); // R0.20
    assertEvents("R0.00", "R0.20", "R0.20");
}

从这里基本可以看出,首先通过 RateLimiter.create 的静态方法创建一个限流器,然后应用程序在执行业务逻辑之前先调研限流器的 acquire 方法申请许可,接下来我们将循着这个流程来探讨其实现思路。

3、探究 SmoothBursty 实现原理


3.1 SmoothBursty 创建流程

从上面的示例来看,应用程序首先通过 RateLimiter 的静态方法创建一个限流器,其代码如下:

RateLimiter#create

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) { // @1
    RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0);                                  // @2
    rateLimiter.setRate(permitsPerSecond);                                                                    // @3
    return rateLimiter;
}

代码@1:首先先介绍方法的参数:

  • SleepingStopwatch stopwatch 秒表,主要是实现当前从启动开始已消耗的时间,有点类似计算一个操作耗时,实现精度纳秒。
  • double permitsPerSecond 每秒的许可数,即通常我们说的限流TPS。

代码@2:创建 SmoothBursty 对象。

代码@3:调用 setRate API 设置其速率器。

接下来我们对其进行展开。

3.1.1 SmoothBursty 构造函数

SmoothBursty 构造函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
    super(stopwatch);
    this.maxBurstSeconds = maxBurstSeconds;
}

这里主要是为 stopWatch 与 maxBurstSeconds 赋值,其中 maxBurstSeconds 为允许的突发流量的时间,这里默认为 1.0,表示一秒,会影响最大可存储的许可数。

3.1.2 RateLimiter setRate 方法详解

RateLimiter#setRate

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final void setRate(double permitsPerSecond) {
    checkArgument(
        permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
    synchronized (mutex()) { // @1
      doSetRate(permitsPerSecond, stopwatch.readMicros()); // @2
    }
}

代码@1:该方法需要获取该类的监视器,在同步代码块中执行,实现线程安全性。

代码@2:调用 doSetRate 设置速率,将调用其具体实现类 SmoothRateLimiter 的 doSetRate 方法。

SmoothRateLimiter#doSetRate

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final void doSetRate(double permitsPerSecond, long nowMicros) { // @1
    resync(nowMicros);   // @2
    double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;  // @3
    this.stableIntervalMicros = stableIntervalMicros;                                              
    doSetRate(permitsPerSecond, stableIntervalMicros);                                      // @4
}

代码@1:先来介绍一下该方法的参数的含义:

  • double permitsPerSecond 每秒的许可数,即TPS。
  • long nowMicros 系统已运行时间。

代码@2:基于当前时间重置 SmoothRateLimiter 内部的 storedPermits (已存储的许可数量) 与 nextFreeTicketMicros (下一次可以免费获取许可的时间) 值,所谓的免费指的是无需等待就可以获取设定速率的许可,该方法对理解限流许可的产生非常关键,稍后详细介绍。

代码@3:根据 TPS 算出一个稳定的获取1个许可的时间。以一秒发放5个许可,即限速为5TPS,那发放一个许可的世界间隔为 200ms,stableIntervalMicros 变量是以微秒为单位。

代码@4:调用 SmoothRateLimiter 的抽象方法 doSetRate 设置速率,这里会调用 SmoothBursty 的 doSetRate 方法。

在介绍 SmoothBursty 的 doSetRate 方法之前,我们先来看看 resync 方法的实现细节。

SmoothRateLimiter#resync

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void resync(long nowMicros) {
    if (nowMicros > nextFreeTicketMicros) {  // @1 
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();  // @2
      storedPermits = min(maxPermits, storedPermits + newPermits);    // @3
      nextFreeTicketMicros = nowMicros;   // @4
    }
}

代码@1:如果当前已启动时间大于 nextFreeTicketMicros(下一次可以免费获取许可的时间),则需要重新计算许可,即又可以向许可池中添加许可。

代码@2:根据当前时间可增加的许可数量,在 SmoothBursty 的 coolDownIntervalMicros 方法返回的就是上文提到的 stableIntervalMicros (发放一个许可所需要的时间),故本次可以增加的许可数的算法也好理解,即用当前时间戳减去 nextFreeTicketMicros 的差值,再除以发送一个许可所需要的时间即可。

代码@3:计算当前可用的许可。

代码@4:更新下一次可增加计算许可的时间。

接下来再继续看 SmoothBursty 的 doSetRate 方法。

SmoothBursty#doSetRate

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
    double oldMaxPermits = this.maxPermits;
    maxPermits = maxBurstSeconds * permitsPerSecond;
    if (oldMaxPermits == Double.POSITIVE_INFINITY) {
        storedPermits = maxPermits;
    } else {
        storedPermits =
            (oldMaxPermits == 0.0)
                ? 0.0 // initial state
                : storedPermits * maxPermits / oldMaxPermits;
    }
}

这里主要是初始化 storedPermits 的值,该限速器支持在运行过程中动态改变 permitsPerSecond 的值。

3.2 SmoothBursty acquire 工作流程

RateLimiter 中的 acquire 方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public double acquire(int permits) {
    long microsToWait = reserve(permits);    // @1
    stopwatch.sleepMicrosUninterruptibly(microsToWait);   // @2
    return 1.0 * microsToWait / SECONDS.toMicros(1L);   // @3
}

代码@1:根据当前剩余的许可与本次申请的许可来判断本次申请需要等待的时长,如果返回0则表示无需等待。

代码@2:如果需要等待的时间不为0,表示触发限速,睡眠指定时间后唤醒。

代码@3:返回本次申请等待的时长。

接下来重点介绍 reserve 方法的实现原理。

RateLimiter#reserve

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final long reserve(int permits) {
    checkPermits(permits);
    synchronized (mutex()) {  // @1
      return reserveAndGetWaitLength(permits, stopwatch.readMicros()); // @2
    }
}

代码@1:限速器主要维护的重要数据字段( storedPermits ),对其进行维护时都需要先获取锁。

代码@2:调用内部方法 reserveAndGetWaitLength 来计算需要等待时间。

继续跟踪 reserveAndGetWaitLength 方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final long reserveAndGetWaitLength(int permits, long nowMicros) {
    long momentAvailable = reserveEarliestAvailable(permits, nowMicros);   // @1
    return max(momentAvailable - nowMicros, 0);  // @2
}

代码@1:根据当前拥有的许可数量、当前时间判断待申请许可最早能得到满足的最早时间,用momentAvailable 表示。

代码@2:然后计算 momentAvailable 与 nowMicros 的差值与0做比较,得出需要等待的时间。

继续跟踪 reserveEarliestAvailable方法,该方法在 RateLimiter 中一个抽象方法,具体实现在其子类 SmoothRateLimiter 中。

SmoothRateLimiter#reserveEarliestAvailable

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    resync(nowMicros);   // @1
    long returnValue = nextFreeTicketMicros;
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits); // @2
    double freshPermits = requiredPermits - storedPermitsToSpend; // @3
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);  // @4

    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);  // @5
    this.storedPermits -= storedPermitsToSpend;    // @6
    return returnValue;
}

代码@1:在尝试申请许可之前,先根据当前时间即发放许可速率更新 storedPermits 与 nextFreeTicketMicros(下一次可以免费获取许可的时间)。

代码@2:计算本次能从 storedPermits 中消耗的许可数量,取需要申请的许可数量与当前可用的许可数量的最小值,用 storedPermitsToSpend 表示。

代码@3:如果需要申请的许可数量( requiredPermits )大于当前剩余许可数量( storedPermits ),则还需要等待新的许可生成,用 freshPermits 表示,即如果该值大于0,则表示本次申请需要阻塞一定时间。

代码@4:计算本次申请需要等待的时间,storedPermitsToWaitTime 方法在 SmoothBursty 的实现中默认返回 0,即 SmoothBursty 的等待时间主要来自按照速率生成 freshPermits 个许可的时间,生成一个许可的时间为 stableIntervalMicros,故需要等待的时长为 freshPermits * stableIntervalMicros。

代码@5:更新 nextFreeTicketMicros 为当前时间加上需要等待的时间。

代码@6:更新 storedPermits 的值,即减少本次已消耗的许可数量。

代码@7:请注意这里返回的 returnValue 的值,并没有包含由于剩余许可需要等待创建新许可的时间,即允许一定的突发流量,故本次计算需要的等待时间将对下一次请求生效,这也是框架作者将该限速器取名为 SmoothBursty 的缘由。

SmoothBursty 的 acquire 方法就介绍到这里了。

4、总结


由于源码分析会显得枯燥与不直观,我们先给出如下流程图:

SmoothBursty 的核心设计思想基本与令牌桶类似,但还是有些不同。基本思想:

  1. SmoothBursty 以指定的速率生成许可,在 SmoothBursty 中用 storedPermits 表示。
  2. 当一个请求需要申请许可时,如果需要申请的许可数小于 storedPermits ,则消耗指定许可,直接返回,无需等待。
  3. 当一个请求需要申请的许可大于 storedPermits 时,则计算需要等待的时间,更新下一次许可可发放时间,直接返回,即当请求消耗掉所有许可后,当前请求并不会阻塞,而是影响下一个请求,即支持突发流量。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 中间件兴趣圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
限流算法(Guava RateLimiter)
这种算法很好实现, 但是会出现限流不准确问题。比如每秒通过 5 个请求,时间窗口的大小为 1 秒,当前时间窗口周期内的后半秒正常通过了 5 个请求,下一个时间窗口周期内的前半秒正常通过了 5 个请求,在这两个窗口内都没有超过限制。 但是在这两个窗口的中间那一秒实际上通过了 10 个请求,显然不满足每秒 5 个请求的限制。
leobhao
2022/06/28
9240
限流算法(Guava RateLimiter)
源码分析RateLimiter SmoothWarmingUp 实现原理(文末附流程图)
上一篇详细介绍了 SmoothBursty 的实现原理,本文将介绍带有预热机制的限速器实现原理。
丁威
2020/03/31
1.6K0
源码分析RateLimiter SmoothWarmingUp 实现原理(文末附流程图)
面试官:来谈谈限流-RateLimiter源码分析
RateLimiter有两个实现类:SmoothBursty和SmoothWarmingUp,其都是令牌桶算法的变种实现,区别在于SmoothBursty加令牌的速度是恒定的,而SmoothWarmingUp会有个预热期,在预热期内加令牌的速度是慢慢增加的,直到达到固定速度为止。其适用场景是,对于有的系统而言刚启动时能承受的QPS较小,需要预热一段时间后才能达到最佳状态。
李红
2019/08/13
8430
Guava RateLimiter限流源码解析和实例应用
Guava有两种限流模式,一种为稳定模式(SmoothBursty:令牌生成速度恒定),一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值) 两种模式实现思路类似,主要区别在等待时间的计算上,本篇重点介绍SmoothBursty
算法之名
2019/08/20
9030
Guava RateLimiter限流源码解析和实例应用
RateLimiter源码分析
如下图所示,我创建一个1秒产生0.1的RateLimiter(即10秒产生1个),左边是时间轴,现在有3个线程申请数据,nextFreeTicketMicros初始化为0(其实他的计算单位是微秒)
CBeann
2023/12/25
2410
RateLimiter源码分析
令牌桶的实现_C语言实现栈
接上篇。Guava的令牌桶的实现中,包括一条设计哲学,需要大家注意:它允许瞬间的流量波峰超过QPS,但瞬间过后的请求将会等待较长的时间来缓解上次的波峰,以使得平均的QPS等于预定值。
全栈程序员站长
2022/09/27
9250
Java系统设计与面试深度解析:高并发限流策略之令牌桶与漏桶对比
在高并发系统设计中,限流策略如同交通信号灯般控制着请求的流量节奏。当系统面临突发流量冲击时——无论是电商平台的秒杀活动,还是社交媒体的热点事件——合理的限流机制能有效防止服务雪崩,保障核心业务持续稳定运行。这种技术手段通过预设的规则对超出系统处理能力的请求进行限制或拒绝,成为现代分布式架构中不可或缺的稳定性保障组件。
用户6320865
2025/08/27
2880
Java系统设计与面试深度解析:高并发限流策略之令牌桶与漏桶对比
Guava RateLimiter详解以及源码分析
首先你需要明白限流的概念,在高并发、高流量的场景中,我们的系统有时候会通过限流的手段来防止自己的系统被外部的流量打挂,是一种自我保护措施。
用户7634691
2021/08/12
1.2K0
基本限流算法与GuavaRateLimiter实现
总结: 固定窗口算法适用于对请求速率有明确要求且流量相对稳定的场景,但对于突发流量和请求分布不均匀的情况,可能需要考虑其他更灵活的限流算法。
leobhao
2024/08/07
2610
基本限流算法与GuavaRateLimiter实现
限速神器RateLimiter源码解析
Tech 导读 在软件系统中,面对高并发的场景,经常需要通过限流来降低系统压力、保护系统不被压垮;另外在交易处理的场景中,也经常因下游要求或其他原因需控制处理速率。RateLimiter是谷歌开源的一款轻巧限流限速组件,简单实用,设计精妙,本文结合示例对其源码进行了相关分析解读,包括代码层级、处理流程、数据流转、计算逻辑等, 希望能够帮助大家了解和使用。
京东技术
2023/08/22
7150
限速神器RateLimiter源码解析
限流原理解读之guava中的RateLimiter
RateLimiter.create做了两件事情创建Bursty对象和设置了速率,至次初始化过程结束
爬蜥
2019/07/08
1.8K0
聊聊Guava的RateLimiter
guava-26.0-jre-sources.jar!/com/google/common/util/concurrent/RateLimiter.java
code4it
2018/09/17
2.9K0
使用Guava RateLimiter限流以及源码解析
首先通过RateLimiter.create(1);创建一个限流器,参数代表每秒生成的令牌数,通过limiter.acquire(i);来以阻塞的方式获取令牌,当然也可以通过tryAcquire(int permits, long timeout, TimeUnit unit)来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回。
用户6182664
2020/05/08
1.4K0
使用Guava RateLimiter限流以及源码解析
超详细的Guava RateLimiter限流原理解析
 限流是保护高并发系统的三把利器之一,另外两个是缓存和降级。限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自身系统和下游系统不被巨型流量冲垮等。
程序员历小冰
2019/03/31
18.8K0
超详细的Guava RateLimiter限流原理解析
令牌桶算法原理及应用
最近在参与一个业务迁移的项目。走读代码时,接触到一些限流相关的代码。向老司机请教后了解到,有些业务承载了很高量级的扣款请求,尤其对于一些热点商户,其单点的请求量很大,但某些瓶颈系统的处理能力有限,因此需要做好限流,以保障业务流程中各系统的稳定性。
张乘辉
2022/01/24
4.4K0
令牌桶算法原理及应用
RateLimiter 的底层实现是啥?
作者:温安适 来源:https://my.oschina.net/floor/blog/4965200
Java技术栈
2021/06/16
3790
​什么是限流,如何限流
某天小明突然发现自己的接口请求突然之间涨到了原来的10倍,接口几乎不能使用,产生了一系列连锁反应,导致了整个系统崩溃。这就好比,老电闸中都安装了保险丝,一旦使用大功率设备,保险丝就会熔断,保证各个电器不被强电流烧坏,系统也同样安装保险丝,防止非预期请求过大,引起系统瘫痪。
王小明_HIT
2021/05/20
3.4K0
源码解析:Guava客户端限流
客户端限流:当应用为单点应用时,只要应用进行了限流,那么应用所依赖的各种服务也都得到了保护。
后台技术汇
2024/09/15
2580
源码解析:Guava客户端限流
高并发之限流,到底限的什么鬼 (精品长文)
你可能知道高并发系统需要限流这个东西,但具体是限制的什么,该如何去做,还是模凌两可。我们接下来系统性的给它归个小类,希望对你有所帮助。
xjjdog
2019/07/10
1.2K0
高并发之限流,到底限的什么鬼 (精品长文)
RateLimiter没有用到集合,核心是一个时间值
令牌桶的图,网上到处可见,按图实现也非常简单,无非是定时添加令牌桶,并提供一个获取令牌的函数,博主实现了一遍代码如下:
温安适
2021/02/27
3460
RateLimiter没有用到集合,核心是一个时间值
相关推荐
限流算法(Guava RateLimiter)
更多 >
领券
一站式MCP教程库,解锁AI应用新玩法
涵盖代码开发、场景应用、自动测试全流程,助你从零构建专属AI助手
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档