本节目录

  • 1、RateLimiterController
  • 2、WarmUpController
    • 2.1 WarmUpController 构造函数
    • 2.2 canPass 方法详解
  • 3、总结

温馨提示,如果大家对源码不感兴趣,可以直接跳到本文的总结部分,了解一下预热实现原理的一些实战建议。

首先先回顾一下 Sentinel 流控效果相关的类图:

DefaultController 快速失败已经在上文详细介绍过,本文将详细介绍其他两种策略的实现原理。

首先我们应该知道,一条流控规则(FlowRule)对应一个 TrafficShapingController 对象。

1、RateLimiterController

匀速排队策略实现类,首先我们先来介绍一下该类的几个成员变量的含义:

  • int maxQueueingTimeMs
    排队等待的最大超时时间,如果等待超过该时间,将会抛出 FlowException。
  • double count
    流控规则中的阔值,即令牌的总个数,以QPS为例,如果该值设置为1000,则表示1s可并发的请求数量。
  • AtomicLong latestPassedTime
    上一次成功通过的时间戳。

接下来我们详细来看一下其算法的实现:
RateLimiterController#canPass

public boolean canPass(Node node, int acquireCount, boolean prioritized) {if (acquireCount <= 0) {return true;}if (count <= 0) {return false;}long currentTime = TimeUtil.currentTimeMillis();long costTime = Math.round(1.0 * (acquireCount) / count * 1000);    // @1long expectedTime = costTime + latestPassedTime.get();                // @2if (expectedTime <= currentTime) {                                                    // @3latestPassedTime.set(currentTime);return true;} else {long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();   // @4if (waitTime > maxQueueingTimeMs) {                                                                        // @5return false;} else {long oldTime = latestPassedTime.addAndGet(costTime);                                     // @6try {waitTime = oldTime - TimeUtil.currentTimeMillis();                                            if (waitTime > maxQueueingTimeMs) {latestPassedTime.addAndGet(-costTime);return false;}if (waitTime > 0) {                                                     // @7Thread.sleep(waitTime);}return true;} catch (InterruptedException e) {}}}return false;
}

代码@1:首先算出每一个请求之间最小的间隔,时间单位为毫秒。例如 cout 设置为 1000,表示一秒可以通过 1000个请求,匀速排队,那每个请求的间隔为 1 / 1000(s),乘以1000将时间单位转换为毫秒,如果一次需要2个令牌,则其间隔时间为2ms,用 costTime 表示。

代码@2:计算下一个请求的期望达到时间,等于上一次通过的时间戳 + costTime ,用 expectedTime 表示。

代码@3:如果 expectedTime 小于等于当前时间,说明在期望的时间没有请求到达,说明没有按照期望消耗令牌,故本次请求直接通过,并更新上次通过的时间为当前时间。

代码@4:如果 expectedTime 大于当前时间,说明还没到令牌发放时间,当前请求需要等待。首先先计算需要等待是时间,用 waitTime 表示。

代码@5:如果计算的需要等待的时间大于允许排队的时间,则返回 false,即本次请求将被限流,返回 FlowException。

代码@6:进入排队,默认是本次请求通过,故先将上一次通过流量的时间戳增加 costTime,然后直接调用 Thread 的 sleep 方法,将当前请求先阻塞一会,然后返回 true 表示请求通过。

匀速排队模式的实现的关键:主要是记录上一次请求通过的时间戳,然后根据流控规则,判断两次请求之间最小的间隔,并加入一个排队时间。

2、WarmUpController

预热策略的实现,首先我们先来介绍一下该类的几个成员变量的含义:

  • double count
    流控规则设定的阔值。
  • int coldFactor
    冷却因子。
  • int warningToken
    告警token,对应 Guava 中的 RateLimiter 中的
  • int maxToken
    double slope
    AtomicLong storedTokens
    AtomicLong lastFilledTime

2.1 WarmUpController 构造函数

内部的构造函数,最终将调用 construct 方法。
WarmUpController#construct

private void construct(double count, int warmUpPeriodInSec, int coldFactor) { // @1if (coldFactor <= 1) {throw new IllegalArgumentException("Cold factor should be larger than 1");}this.count = count;  this.coldFactor = coldFactor;   warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);   // @2maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));  // @3slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
}

要理解该方法,就需要理解 Guava 框架的 SmoothWarmingUp 相关的预热算法,其算法原理如图所示:

关于该图的详细介绍,请参考笔者的另外一篇博文:源码分析RateLimiter SmoothWarmingUp 实现原理,对该图进行了详细解读。

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

  • double count
    限流规则配置的阔值,例如是按 TPS 类型来限流,如果限制为100tps,则该值为100。
  • int warmUpPeriodInSec
    预热时间,单位为秒,通用在限流规则页面可配置。
  • int coldFactor
    冷却因子,这里默认为3,与 RateLimiter 中的冷却因子保持一致,表示的含义为 coldIntervalMicros 与 stableIntervalMicros 的比值。

代码@2:计算 warningToken 的值,与 Guava 中的 RateLimiter 中的 thresholdPermits 的计算算法公式相同,thresholdPermits = 0.5 * warmupPeriod / stableInterval,在Sentienl 中,而 stableInteral = 1 / count,thresholdPermits 表达式中的 0.5 就是因为 codeFactor 为3,因为 warm up period与 stable 面积之比等于 (coldIntervalMicros - stableIntervalMicros ) 与 stableIntervalMicros 的比值,这个比值又等于 coldIntervalMicros / stableIntervalMicros - stableIntervalMicros / stableIntervalMicros 等于 coldFactor - 1。

代码@3:同样根据 Guava 中的 RateLimiter 关于 maxToken 也能理解。

2.2 canPass 方法详解

WarmUpController#canPass

public boolean canPass(Node node, int acquireCount, boolean prioritized) {long passQps = (long) node.passQps(); // @1long previousQps = (long) node.previousPassQps();  // @2syncToken(previousQps);  // @3// 开始计算它的斜率// 如果进入了警戒线,开始调整他的qpslong restToken = storedTokens.get();if (restToken >= warningToken) {    // @4long aboveToken = restToken - warningToken;// 消耗的速度要比warning快,但是要比慢// current interval = restToken*slope+1/countdouble warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));if (passQps + acquireCount <= warningQps) {return true;}} else {   // @5if (passQps + acquireCount <= count) {return true;}}return false;
}

代码@1:先获取当前节点已通过的QPS。

代码@2:获取当前滑动窗口的前一个窗口收集的已通过QPS。

代码@3:调用 syncToken 更新 storedTokens 与 lastFilledTime 的值,即按照令牌发放速率发送指定令牌,将在下文详细介绍 syncToken 方法内部的实现细节。

代码@4:如果当前存储的许可大于warningToken的处理逻辑,主要是在预热阶段允许通过的速率会比限流规则设定的速率要低,判断是否通过的依据就是当前通过的TPS与申请的许可数是否小于当前的速率(这个值加入斜率,即在预热期间,速率是慢慢达到设定速率的。

代码@5:当前存储的许可小于warningToken,则按照规则设定的速率进行判定。

不知大家有没有一个疑问,为什么 storedTokens 剩余许可数越大,限制其通过的速率竟然会越慢,这又怎么理解呢?大家可以思考一下这个问题,将在本文的总结部分进行解答。

我们先来看一下 syncToken 的实现细节,即更新 storedTokens 的逻辑。
WarmUpController#syncToken

protected void syncToken(long passQps) {long currentTime = TimeUtil.currentTimeMillis();currentTime = currentTime - currentTime % 1000;    // @1long oldLastFillTime = lastFilledTime.get();if (currentTime <= oldLastFillTime) {                          // @2return;}long oldValue = storedTokens.get();long newValue = coolDownTokens(currentTime, passQps);   // @3if (storedTokens.compareAndSet(oldValue, newValue)) {  long currentValue = storedTokens.addAndGet(0 - passQps);    // @4if (currentValue < 0) {storedTokens.set(0L);}lastFilledTime.set(currentTime);}
}

代码@1:这个是计算出当前时间秒的最开始时间。例如当前是 2020-04-06 08:29:01:056,该方法返回的时间为 2020-04-06 08:29:01:000。

代码@2:如果当前时间小于等于上次发放许可的时间,则跳过,无法发放令牌,即每秒发放一次令牌。

代码@3:具体方法令牌的逻辑,稍后详细介绍。

代码@4:更新剩余令牌,即生成的许可后要减去上一秒通过的令牌。

我们详细来看一下 coolDownTokens 方法。
WarmUpController#coolDownTokens

private long coolDownTokens(long currentTime, long passQps) {long oldValue = storedTokens.get();long newValue = oldValue;// 添加令牌的判断前提条件:// 当令牌的消耗程度远远低于警戒线的时候if (oldValue < warningToken) {    // @1newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);} else if (oldValue > warningToken) {   // @2if (passQps < (int)count / coldFactor) {newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);}}return Math.min(newValue, maxToken);// @3
}

代码@1:如果当前剩余的 token 小于警戒线,可以按照正常速率发放许可。

代码@2:如果当前剩余的 token 大于警戒线但前一秒的QPS小于 (count 与 冷却因子的比),也发放许可(这里我不是太明白其用意)。

代码@3:这里是关键点,第一次运行,由于 lastFilledTime 等于0,这里将返回的是 maxToken,故这里一开始的许可就会超过 warningToken,启动预热机制,进行速率限制。

3、总结

WarmUpController 这个预热算法还是挺复杂的,接下来我们来总结一下它的特征。

不知大家有没有一个疑问,为什么 storedTokens 剩余许可数越大,限制其通过的速率竟然会越慢,这又怎么理解呢?

这里感觉有点逆向思维的味道,因为一开始就会将 storedTokens 的值设置为 maxToken,即开始就会超过 warningToken,从而一开始进入到预热阶段,此时的速率有一个爬坡的过程,类似于数学中的斜率,达到其他启动预热的效果。

实战指南:注意 warmUpPeriodInSec 与 coldFactor 的设置,将会影响最终的限流效果。

为了更加直观的理解,我们举例如下,warningToken 与 maxToken 的生成公式如下:

warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));

coldFactor 设定为 3,例如限流规则中配置每秒允许通过的许可数量为 10,即 count 值等于 10,我们改变 warmUpPeriodInSec 的值来看一下 warningToken 与 maxToken 的值,以此来探究 Sentinel WarmUpController 的工作机制或工作效果。

warmUpPeriodInSec warningToken maxToken
1 5 10
2 10 20
3 15 30
4 20 40

根据上面的算法,如果 warningToken 的值小于 count,则限流会变的更严厉,即最终的限流TPS会小于设置的TPS。即 warmUpPeriodInSec 设置过大过小都不合适,其标准是要使得 warningToken 的值大于 count。

如果文章对你有所帮助的话,还请点个赞,谢谢。


欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
1、源码分析RocketMQ专栏(40篇+)
2、源码分析Sentinel专栏(12篇+)
3、源码分析Dubbo专栏(28篇+)
4、源码分析Mybatis专栏
5、源码分析Netty专栏(18篇+)
6、源码分析JUC专栏
7、源码分析Elasticjob专栏
8、Elasticsearch专栏(20篇+)
9、源码分析MyCat专栏

Sentienl 流控效果之匀速排队与预热实现原理与实战建议相关推荐

  1. Sentinel流控效果—Warm Up

    流控效果Warm Up有什么作用? 比如有一个系统平常没人访问,突然某个时刻系统访问量达到最大:这样的话系统容易崩掉,所以需要预热,让请求慢慢的升高: 比如:秒杀系统在开启的瞬间,会有很多流量上来,很 ...

  2. Sentinel限流--流控模式与限流效果

    文章目录 1.簇点链路 2.流控入门案例 3.流控模式:关联模式 4.流控模式:链路模式 5.流控效果:warm up 6.限流效果:排队等待 7.热点参数限流 1.簇点链路 簇点链路就是项目内的调用 ...

  3. Spring Cloud Alibaba:Sentinel 流控规则

    文章目录 1. 前言 2. 阈值类型 2.1 QPS 2.2 线程数 3. 流控模式 3.1 直接 3.2 关联 3.3 链路 4. 流控效果 4.1 快速失败 4.2 Warm Up 4.3 排队等 ...

  4. sentinel 不显示项目_Sentinel+Nacos实现资源流控、降级、热点、授权

    本文同名博客老炮说Java:https://www.laopaojava.com/,每天更新Spring/SpringMvc/SpringBoot/实战项目等文章资料 Sentinel+Nacos 是 ...

  5. Spring Cloud Alibaba 实战 | 第十二篇: 微服务整合Sentinel的流控、熔断降级,赋能拥有降级功能的Feign新技能熔断,实现熔断降级双剑合璧(JMeter模拟测试)

    文章目录 一. Sentinel概念 1. 什么是Sentinel? 2. Sentinel功能特性 3. Sentinel VS Hystrix 二. Docker部署Sentinel Dashbo ...

  6. 什么?Sentinel流控规则可以这样玩?

    点赞再看,养成习惯,微信搜索[牧小农]关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友. 项目源码地址:公众号回复 sentinel,即可免费获取源码 前言 上一篇文章中,我们讲解了关于 ...

  7. Sentinel 流控(限流)

    流量控制(Flow Control),原理是监控应用流量的QPS或并发线程数等指标,当达到指定阈值时对流量进行控制,避免系统被瞬时的流量高峰冲垮,保障应用高可用性. 通过流控规则来指定允许该资源通过的 ...

  8. Sentinel流控规则

    Sentinel流控规则 流控规则基本介绍 名词解释 资源名:唯一名称,默认请求路径 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源) 阈值类型/单机阈 ...

  9. 【sentinel】流控规则详解

    流量控制规则,简称流控规则,会对资源的流量进行限制.同一个资源可以对应多条限流规则.Sentinel会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕. 限流的直接表现是抛出Fl ...

最新文章

  1. 五子棋博弈树剪枝c语言,五子棋AI博弈树之带Alpha-Beta剪枝的极大极小过程函数...
  2. Kotlin的2017年总结与2018年展望
  3. Halcon算子:min_max_gray和gray_histo的区别
  4. linux密文解密工具,Linux之加密解密工具openssl的用法以及自建CA
  5. python图像质量评价_OpenCV图像质量评价的SSIM算法(图像相似度)
  6. 显示和隐藏菜单栏(两种方式div、table)
  7. RocketMq 消费消息的两种方式 pull 和 push
  8. iframe页面改动parent页面的隐藏input部件value值,不能触发change事件。
  9. 检查硬件变化的命令kudzu
  10. JS中移动端项目取余数和switch于PC端的不同
  11. SSO单点登录之同域登录的实现
  12. 鹏芯U盘(UDK2008)意外断电后修复 1
  13. AVR PIC单片机视频教程
  14. C++ 强连通分量 - 缩点(Tarjan算法)
  15. 《UnityAPI.Texture纹理》(Yanlz+Unity+SteamVR+云技术+5G+AI+VR云游戏+Texture+mipMapBias+wrapMode+立钻哥哥++OK++)
  16. python基于qq邮箱群发邮件
  17. 互联网,大数据和人工智能对我们的生活带来的影响
  18. 微信开发者模式php,php 开启微信公众号开发者模式
  19. 加密狗圣天诺LDK V7.5特性
  20. 光纤接入设备及使用图解

热门文章

  1. Copilot免费时代结束!正式版67元/月,学生党和热门开源项目维护者可白嫖
  2. 单网口电脑安装openwrt软路由做单臂路由
  3. JS读取Cookie
  4. 中国网民总结十大最反感词句(copy)
  5. Adobe CS5全套软件官方下载地址(简体中文完整版)
  6. Hive列转行 (Lateral View + explode)详解
  7. aix ntp 配置_IBM AIX系统NTP配置方法
  8. Python3 日期时间 相关模块(time(时间) / datatime(日期时间) / calendar(日历))
  9. Vulkan 预旋转处理设备方向
  10. 【杂记】(富文本框回填值、ajax数据回填按钮年级学科、去除数组中数组外包的引号、多重循环的写法、微测评获奖页面的内容 循环拼接写法、textarea禁止拖动、html基本、透明度、页面内出现滚动条)