double coldIntervalMicros = stableIntervalMicros * coldFactor; // @1
thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros; // @2
maxPermits =
thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros); // @3
slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits); // @4
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
storedPermits = 0.0;
} else {
storedPermits =
(oldMaxPermits == 0.0)
-
? maxPermits // initial state is cold
- storedPermits * maxPermits / oldMaxPermits; // @5
}
}
代码@1:根据冷却因子(coldFactor)来计算冷却间隔(单位为微秒),等于冷却因子与 stableIntervalMicros 的乘积。从这里我们可以得出如下几个基本的概念。冷却因子 coldFactor 为 冷却间隔与稳定间隔的比例。
代码@2:通过 warmupPeriod/2 = thresholdPermits * stableIntervalMicros 等式,求出 thresholdPermits 的值。
代码@3:根据 warmupPeriod = 0.5 * (stableInterval + coldInterval) * (maxPermits - thresholdPermits) 表示可求出 maxPermits 的数量。
代码@4:斜率,表示的是从 stableIntervalMicros 到 coldIntervalMicros 这段时间,许可数量从 thresholdPermits 变为 maxPermits 的增长速率。
代码@5:根据 maxPermits 更新当前存储的许可,即当前剩余可消耗的许可数量。
首先 acquire 的定义在其父类,这里是典型的模板模式,由其父类定义基本流程,由具体的子类实现其特定功能。RateLimiter 中的 acquire 方法如下:
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
inal long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) { // @1
return reserveAndGetWaitLength(permits, stopwatch.readMicros()); // @2
}
}
代码@1:限速器主要维护的重要数据字段(storedPermits),对其进行维护时都需要先获取锁。
代码@2:调用内部方法 reserveAndGetWaitLength 来计算需要等待时间。
继续跟踪 reserveAndGetWaitLength 方法。
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
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 方法返回的,另外一部分以稳定速率生成需要的许可,其需要时间为 freshPermits * stableIntervalMicros,稍后我们详细分析一下 storedPermitsToWaitTime 方法的实现。
代码@5:更新 nextFreeTicketMicros 为当前时间加上需要等待的时间。
代码@6:更新 storedPermits 的值,即减少本次已消耗的许可数量。
代码@7:请注意这里返回的 returnValue 的值,并没有包含由于剩余许可需要等待创建新许可的时间,即允许一定的突发流量,故本次计算需要的等待时间将对下一次请求生效。
接下来重点探讨一下 SmoothWarmingUp 的 storedPermitsToWaitTime 方法。
SmoothWarmingUp#SmoothWarmingUp
long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { // @1
double availablePermitsAboveThreshold = storedPermits - thresholdPermits; // @2
long micros = 0;
if (availablePermitsAboveThreshold > 0.0) { // @3
double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake); // @31
// TODO(cpovirk): Figure out a good name for this variable.
double length = permitsToTime(availablePermitsAboveThreshold)
- permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); // @32
micros = (long) (permitsAboveThresholdToTake * length / 2.0); // @33
permitsToTake -= permitsAboveThresholdToTake; // @34
}
// measuring the integral on the left part of the function (the horizontal line)
micros += (stableIntervalMicros * permitsToTake); // @4
return micros;
}
代码@1:首先介绍其两个参数的含义:
- double storedPermits
当前存储的许可数量。
- double permitsToTake
本次申请需要的许可数量。
代码@2:availablePermitsAboveThreshold ,当前超出 thresholdPermits 的许可个数,如果超过 thresholdPermits ,申请许可将来源于超过的部分,只有其不足后,才会从 thresholdPermits 中申请,这部分的详细逻辑见代码@3。
代码@3:如果当前存储的许可数量超过了稳定许可 thresholdPermits,即存在预热的许可数量的申请逻辑,其实现关键点如下:
-
获取本次从预热区间申请的许可数量。
-
从预热区间获取一个许可的时间其算法有点晦涩难懂,具体实现为@32~@34。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
我的面试宝典:一线互联网大厂Java核心面试题库
以下是我个人的一些做法,希望可以给各位提供一些帮助:
整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!
283页的Java进阶核心pdf文档
Java部分:Java基础,集合,并发,多线程,JVM,设计模式
数据结构算法:Java算法,数据结构
开源框架部分:Spring,MyBatis,MVC,netty,tomcat
分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等
微服务部分:SpringBoot,SpringCloud,Dubbo,Docker
还有源码相关的阅读学习
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
:Spring,MyBatis,MVC,netty,tomcat
分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等
微服务部分:SpringBoot,SpringCloud,Dubbo,Docker
[外链图片转存中…(img-j4xLDKYN-1712486851652)]
还有源码相关的阅读学习
[外链图片转存中…(img-8T7bgXTq-1712486851652)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!