docs: improve sentinel articles
This commit is contained in:
parent
fa1aadc88f
commit
a16b3236f0
25
README.md
25
README.md
@ -337,6 +337,27 @@ GitHub 技术社区 [Doocs](https://github.com/doocs),致力于打造一个内
|
|||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST: END -->
|
<!-- ALL-CONTRIBUTORS-LIST: END -->
|
||||||
|
|
||||||
## 请小码农喝杯 coffee 吧
|
## 公众号
|
||||||
|
|
||||||

|
[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="width: 200px;">
|
||||||
|
<a href="https://github.com/doocs">
|
||||||
|
<img src="./images/qrcode-for-doocs.jpg" style="width: 400px;"><br>
|
||||||
|
<sub>公众平台</sub>
|
||||||
|
</a><br>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="width: 200px;">
|
||||||
|
<a href="https://github.com/yanglbme">
|
||||||
|
<img src="./images/qrcode-for-yanglbme.jpg" style="width: 400px;"><br>
|
||||||
|
<sub>个人微信</sub>
|
||||||
|
</a><br>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取 [doocs/advanced-java](https://github.com/doocs/advanced-java) 项目离线 PDF 文档(283 页精华),学习更加方便!
|
||||||
|
|
||||||
|

|
||||||
|
@ -11,14 +11,14 @@
|
|||||||
4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。
|
4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。
|
||||||
其中的第四个情况表明,sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。
|
其中的第四个情况表明,sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。
|
||||||
|
|
||||||
```Java
|
```java
|
||||||
protected final AtomicReferenceArray<WindowWrap<T>> array;
|
protected final AtomicReferenceArray<WindowWrap<T>> array;
|
||||||
```
|
```
|
||||||
|
|
||||||
在 LeapArray 中,时间窗口的存放通过一个由 AtomicReferenceArray 实现的 array 来实现。AtomicReferenceArray 支持原子读取和写入,并支持通过 cas 来为指定位置的成员进行更新。在时间窗口的创建并放回 array 的过程中,也就是上文的第一步,就是通过 AtomicReferenceArray 的 compareAndSet()方法来实现,保证并发下的线程安全。并发情况下,通过 cas 更新失败的线程将会回到就绪态,在下一次婚欢得到已经初始化完成的时间窗口。
|
在 LeapArray 中,时间窗口的存放通过一个由 AtomicReferenceArray 实现的 array 来实现。AtomicReferenceArray 支持原子读取和写入,并支持通过 cas 来为指定位置的成员进行更新。在时间窗口的创建并放回 array 的过程中,也就是上文的第一步,就是通过 AtomicReferenceArray 的 compareAndSet()方法来实现,保证并发下的线程安全。并发情况下,通过 cas 更新失败的线程将会回到就绪态,在下一次婚欢得到已经初始化完成的时间窗口。
|
||||||
|
|
||||||
```Java
|
```java
|
||||||
private final ReentrantLock updateLock = new ReentrantLock();
|
private final ReentrantLock updateLock = new ReentrantLock();
|
||||||
```
|
```
|
||||||
|
|
||||||
此处的 updateLock 是专门在上述的第三个情况来进行加锁的,只有成功得到锁的线程才会对过期的时间窗口进行 reset 操作,其他没有成功获取的线程将不会挂起等待,而是通过 yield()方法回到就绪态在下一次的循环尝试重新获取该位置的时间窗口。在下一次获取该锁的线程可能已经完成了,那么将会执行上述第二步,否则继续回到就绪态等待下一次循环中再次获取该时间窗口。
|
此处的 updateLock 是专门在上述的第三个情况来进行加锁的,只有成功得到锁的线程才会对过期的时间窗口进行 reset 操作,其他没有成功获取的线程将不会挂起等待,而是通过 yield()方法回到就绪态在下一次的循环尝试重新获取该位置的时间窗口。在下一次获取该锁的线程可能已经完成了,那么将会执行上述第二步,否则继续回到就绪态等待下一次循环中再次获取该时间窗口。
|
||||||
|
@ -2,53 +2,53 @@
|
|||||||
|
|
||||||
Sentinel 中漏桶算法通过 RateLimiterController 来实现,在漏桶算法中,会记录上一个请求的到达时间,如果新到达的请求与上一次到达的请求之间的时间差小于限流配置所规定的最小时间,新到达的请求将会排队等待规定的最小间隔到达,或是直接失败。
|
Sentinel 中漏桶算法通过 RateLimiterController 来实现,在漏桶算法中,会记录上一个请求的到达时间,如果新到达的请求与上一次到达的请求之间的时间差小于限流配置所规定的最小时间,新到达的请求将会排队等待规定的最小间隔到达,或是直接失败。
|
||||||
|
|
||||||
```Java
|
```java
|
||||||
@Override
|
@Override
|
||||||
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
|
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
|
||||||
if (acquireCount <= 0) {
|
if (acquireCount <= 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count <= 0) {
|
if (count <= 0) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long currentTime = TimeUtil.currentTimeMillis();
|
|
||||||
// 根据配置计算两次请求之间的最小时间
|
|
||||||
long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
|
|
||||||
|
|
||||||
// 计算上一次请求之后,下一次允许通过的最小时间
|
|
||||||
long expectedTime = costTime + latestPassedTime.get();
|
|
||||||
|
|
||||||
if (expectedTime <= currentTime) {
|
|
||||||
// 如果当前时间大于计算的时间,那么可以直接放行
|
|
||||||
latestPassedTime.set(currentTime);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// 如果没有,则计算相应需要等待的时间
|
|
||||||
long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
|
|
||||||
if (waitTime > maxQueueingTimeMs) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
long oldTime = latestPassedTime.addAndGet(costTime);
|
|
||||||
try {
|
|
||||||
waitTime = oldTime - TimeUtil.currentTimeMillis();
|
|
||||||
// 如果最大等待时间小于需要等待的时间,那么返回失败,当前请求被拒绝
|
|
||||||
if (waitTime > maxQueueingTimeMs) {
|
|
||||||
latestPassedTime.addAndGet(-costTime);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 在并发条件下等待时间可能会小于等于0
|
|
||||||
if (waitTime > 0) {
|
|
||||||
Thread.sleep(waitTime);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long currentTime = TimeUtil.currentTimeMillis();
|
||||||
|
// 根据配置计算两次请求之间的最小时间
|
||||||
|
long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
|
||||||
|
|
||||||
|
// 计算上一次请求之后,下一次允许通过的最小时间
|
||||||
|
long expectedTime = costTime + latestPassedTime.get();
|
||||||
|
|
||||||
|
if (expectedTime <= currentTime) {
|
||||||
|
// 如果当前时间大于计算的时间,那么可以直接放行
|
||||||
|
latestPassedTime.set(currentTime);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// 如果没有,则计算相应需要等待的时间
|
||||||
|
long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
|
||||||
|
if (waitTime > maxQueueingTimeMs) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
long oldTime = latestPassedTime.addAndGet(costTime);
|
||||||
|
try {
|
||||||
|
waitTime = oldTime - TimeUtil.currentTimeMillis();
|
||||||
|
// 如果最大等待时间小于需要等待的时间,那么返回失败,当前请求被拒绝
|
||||||
|
if (waitTime > maxQueueingTimeMs) {
|
||||||
|
latestPassedTime.addAndGet(-costTime);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 在并发条件下等待时间可能会小于等于0
|
||||||
|
if (waitTime > 0) {
|
||||||
|
Thread.sleep(waitTime);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sentinel 中令牌桶算法的实现
|
## Sentinel 中令牌桶算法的实现
|
||||||
@ -56,84 +56,84 @@ Sentinel 中漏桶算法通过 RateLimiterController 来实现,在漏桶算法
|
|||||||
在 Sentinel 中,令牌桶算法通过 WarmUpController 类实现。在这个情况下,当配置每秒能通过多少请求后,那么在这里 sentinel 也会每秒往桶内添加多少的令牌。当一个请求进入的时候,将会从中移除一个令牌。由此可以得出,桶内的令牌越多,也说明当前的系统利用率越低。因此,当桶内的令牌数量超过某个阈值后,那么当前的系统可以称之为处于`饱和`状态。
|
在 Sentinel 中,令牌桶算法通过 WarmUpController 类实现。在这个情况下,当配置每秒能通过多少请求后,那么在这里 sentinel 也会每秒往桶内添加多少的令牌。当一个请求进入的时候,将会从中移除一个令牌。由此可以得出,桶内的令牌越多,也说明当前的系统利用率越低。因此,当桶内的令牌数量超过某个阈值后,那么当前的系统可以称之为处于`饱和`状态。
|
||||||
当系统处于 `饱和`状态的时候,当前允许的最大 qps 将会随着剩余的令牌数量减少而缓慢增加,达到为系统预热热身的目的。
|
当系统处于 `饱和`状态的时候,当前允许的最大 qps 将会随着剩余的令牌数量减少而缓慢增加,达到为系统预热热身的目的。
|
||||||
|
|
||||||
```Java
|
```java
|
||||||
this.count = count;
|
this.count = count;
|
||||||
|
|
||||||
this.coldFactor = coldFactor;
|
this.coldFactor = coldFactor;
|
||||||
|
|
||||||
warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
|
warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
|
||||||
|
|
||||||
maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));
|
maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));
|
||||||
|
|
||||||
slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
|
slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
|
||||||
```
|
```
|
||||||
|
|
||||||
其中 count 是当前 qps 的阈值。coldFactor 则为冷却因子,warningToken 则为警戒的令牌数量,warningToken 的值为(热身时间长度 _ 每秒令牌的数量) / (冷却因子 - 1)。maxToken 则是最大令牌数量,具体的值为 warningToken 的值加上 (2 _ 热身时间长度 _ 每秒令牌数量) / (冷却因子 + 1)。当当前系统处于热身时间内,其允许通过的最大 qps 为 1 / (超过警戒数的令牌数 _ 斜率 slope + 1 / count),而斜率的值为(冷却因子 - 1) / count / (最大令牌数 - 警戒令牌数)。
|
其中 count 是当前 qps 的阈值。coldFactor 则为冷却因子,warningToken 则为警戒的令牌数量,warningToken 的值为(热身时间长度 _ 每秒令牌的数量) / (冷却因子 - 1)。maxToken 则是最大令牌数量,具体的值为 warningToken 的值加上 (2 _ 热身时间长度 _ 每秒令牌数量) / (冷却因子 + 1)。当当前系统处于热身时间内,其允许通过的最大 qps 为 1 / (超过警戒数的令牌数 _ 斜率 slope + 1 / count),而斜率的值为(冷却因子 - 1) / count / (最大令牌数 - 警戒令牌数)。
|
||||||
举个例子: count = 3, coldFactor = 3,热身时间为 4 的时候,警戒令牌数为 6,最大令牌数为 12,当剩余令牌处于 6 和 12 之间的时候,其 slope 斜率为 1 / 9。 那么当剩余令牌数为 9 的时候的允许 qps 为 1.5。其 qps 将会随着剩余令牌数的不断减少而直到增加到 count 的值。
|
举个例子: count = 3, coldFactor = 3,热身时间为 4 的时候,警戒令牌数为 6,最大令牌数为 12,当剩余令牌处于 6 和 12 之间的时候,其 slope 斜率为 1 / 9。 那么当剩余令牌数为 9 的时候的允许 qps 为 1.5。其 qps 将会随着剩余令牌数的不断减少而直到增加到 count 的值。
|
||||||
|
|
||||||
```Java
|
```java
|
||||||
@Override
|
@Override
|
||||||
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
|
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
|
||||||
long passQps = (long) node.passQps();
|
long passQps = (long) node.passQps();
|
||||||
|
|
||||||
long previousQps = (long) node.previousPassQps();
|
long previousQps = (long) node.previousPassQps();
|
||||||
// 首先重新计算其桶内剩余的数量
|
// 首先重新计算其桶内剩余的数量
|
||||||
syncToken(previousQps);
|
syncToken(previousQps);
|
||||||
|
|
||||||
// 开始计算它的斜率
|
// 开始计算它的斜率
|
||||||
// 如果进入了警戒线,开始调整他的qps
|
// 如果进入了警戒线,开始调整他的qps
|
||||||
long restToken = storedTokens.get();
|
long restToken = storedTokens.get();
|
||||||
if (restToken >= warningToken) {
|
if (restToken >= warningToken) {
|
||||||
long aboveToken = restToken - warningToken;
|
long aboveToken = restToken - warningToken;
|
||||||
// 如果当前剩余的令牌数大于警戒数,那么需要根据准备的计算公式重新计算qps,这个qps小于设定的阈值
|
// 如果当前剩余的令牌数大于警戒数,那么需要根据准备的计算公式重新计算qps,这个qps小于设定的阈值
|
||||||
double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
|
double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
|
||||||
if (passQps + acquireCount <= warningQps) {
|
if (passQps + acquireCount <= warningQps) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (passQps + acquireCount <= count) {
|
if (passQps + acquireCount <= count) {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void syncToken(long passQps) {
|
return false;
|
||||||
long currentTime = TimeUtil.currentTimeMillis();
|
}
|
||||||
currentTime = currentTime - currentTime % 1000;
|
|
||||||
long oldLastFillTime = lastFilledTime.get();
|
|
||||||
if (currentTime <= oldLastFillTime) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long oldValue = storedTokens.get();
|
|
||||||
long newValue = coolDownTokens(currentTime, passQps);
|
|
||||||
|
|
||||||
if (storedTokens.compareAndSet(oldValue, newValue)) {
|
|
||||||
// 从桶内移除相应数量的令牌,并更新最后更新时间
|
|
||||||
long currentValue = storedTokens.addAndGet(0 - passQps);
|
|
||||||
if (currentValue < 0) {
|
|
||||||
storedTokens.set(0L);
|
|
||||||
}
|
|
||||||
lastFilledTime.set(currentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
protected void syncToken(long passQps) {
|
||||||
|
long currentTime = TimeUtil.currentTimeMillis();
|
||||||
|
currentTime = currentTime - currentTime % 1000;
|
||||||
|
long oldLastFillTime = lastFilledTime.get();
|
||||||
|
if (currentTime <= oldLastFillTime) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long coolDownTokens(long currentTime, long passQps) {
|
long oldValue = storedTokens.get();
|
||||||
long oldValue = storedTokens.get();
|
long newValue = coolDownTokens(currentTime, passQps);
|
||||||
long newValue = oldValue;
|
|
||||||
|
|
||||||
// 当令牌的消耗程度远远低于警戒线的时候,将会补充令牌数
|
if (storedTokens.compareAndSet(oldValue, newValue)) {
|
||||||
if (oldValue < warningToken) {
|
// 从桶内移除相应数量的令牌,并更新最后更新时间
|
||||||
|
long currentValue = storedTokens.addAndGet(0 - passQps);
|
||||||
|
if (currentValue < 0) {
|
||||||
|
storedTokens.set(0L);
|
||||||
|
}
|
||||||
|
lastFilledTime.set(currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private long coolDownTokens(long currentTime, long passQps) {
|
||||||
|
long oldValue = storedTokens.get();
|
||||||
|
long newValue = oldValue;
|
||||||
|
|
||||||
|
// 当令牌的消耗程度远远低于警戒线的时候,将会补充令牌数
|
||||||
|
if (oldValue < warningToken) {
|
||||||
|
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
|
||||||
|
} else if (oldValue > warningToken) {
|
||||||
|
if (passQps < (int)count / coldFactor) {
|
||||||
|
// qps小于阈值 / 冷却因子的时候,说明此时还不需要根据剩余令牌数调整qps的阈值,所以也会补充
|
||||||
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
|
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
|
||||||
} else if (oldValue > warningToken) {
|
|
||||||
if (passQps < (int)count / coldFactor) {
|
|
||||||
// qps小于阈值 / 冷却因子的时候,说明此时还不需要根据剩余令牌数调整qps的阈值,所以也会补充
|
|
||||||
newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Math.min(newValue, maxToken);
|
|
||||||
}
|
}
|
||||||
|
return Math.min(newValue, maxToken);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
BIN
images/pdf.png
Normal file
BIN
images/pdf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 169 KiB |
BIN
images/qrcode-for-doocs.jpg
Normal file
BIN
images/qrcode-for-doocs.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
BIN
images/qrcode-for-yanglbme.jpg
Normal file
BIN
images/qrcode-for-yanglbme.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
Loading…
x
Reference in New Issue
Block a user