在前面搞清楚了Sentinel的使用后,大致理了一下Sentinel的责任链,搞清楚了这个,基本就已经梳理清楚sentinel-core模块的大部分内容,顺着这条链路可以继续梳理很多东西。

知其然、知其所以然。而阅读源码就是最好的知其所以然的方式。这一次找了一些空闲时间,捋了一下它的滑动窗口算法,在这里做一个记录。后面会继续去梳理它的令牌算法和漏桶算法。

关于滑动窗口的原理,Sentinel为什么要使用滑动窗口,Sentinel是怎样使用的滑动,直接使用下面这两张图。一图胜千言,一张好的图足以说明问题,在这里我引用两张图。

图片说明:第一张图为Sentinel github上的图片,因为有时加载不出来,所以拷贝出来了。图二为一张微信公众号的图片,具体公众号见水印。这里引用只是为了学习使用,但是还是注明一下来源。

首先从StatisticSlot类开始,它是Sentinel统计的核心功能槽,先看它的entry[^对这个方法做了一下精简,只保留了几行能够说明问题的代码。]方法:

@SpiOrder(-7000)

public class StatisticSlot extends AbstractLinkedProcessorSlot {

@Override

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,

boolean prioritized, Object... args) throws Throwable {

try {

// 先执行后续限流、降级等功能

fireEntry(context, resourceWrapper, node, count, prioritized, args);

// 上面执行通过,更新通过请求数据

node.addPassRequest(count);

} catch (PriorityWaitException ex) {

} catch (BlockException e) {

// 上面执行阻塞,更新阻塞请求数据

node.increaseBlockQps(count);

} catch (Throwable e) {

}

}

}

通过代码可以看出,它是先执行后面的限流、降级等,然后以后面的执行结果为基础来更新对应资源的通过、阻塞、异常等统计数据。上面的执行通过和异常处理逻辑大体一致。这里就以执行通过这条线来说明问题,所以对应代码就是node.addPassRequest(count);进入到这一行代码,经过几次调用转到了StatisticNode这个类上,根据类名可以知道这个表示一个统计节点,调用的方法是addPassRequest:

@Override

public void addPassRequest(int count) {

rollingCounterInSecond.addPass(count);

rollingCounterInMinute.addPass(count);

}

由这个方法可以看出,StatisticNode在处理统计数据的时候,分了两个维度,分别是秒级的和分钟级的。对应的rollingCounterInSecond和rollingCounterInMinute是它的两个成员属性。其定义如下:

public class StatisticNode implements Node {

/**

* Holds statistics of the recent {@code INTERVAL} seconds. The {@code INTERVAL} is divided into time spans

* by given {@code sampleCount}.

*/

private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,

IntervalProperty.INTERVAL);

/**

* Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds,

* meaning each bucket per second, in this way we can get accurate statistics of each second.

*/

private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);

}

Metric是一个度量单位接口,其具体实现下面需要提一下,在这里先不展开,只需要知道它存储的是一些执行数据,如成功数、异常数等。而在上面的StatisticNode.addPassRequest方法中就是分别调用两个维度的统计单位增加请求通过数量。从rollingCounterInSecond.addPass(count)这一句进入,对应的方法在ArrayMetric类中,这一个就是Metric的唯一实现。ArrayMetric.addPass这个方法代码如下:

@Override

public void addPass(int count) {

// 获取当前时间对应的窗口,返回的是当前窗口的一个包装类

WindowWrap wrap = data.currentWindow();

wrap.value().addPass(count);

}

可以看出这里就已经是滑动窗口算法的入口了。通过滑动窗口算法,使用当前时间获取一个合适的窗口,然后在这个窗口中增加通过的请求数。进入到代码里面,最终落实到了LeapArray的currentWindow方法中了。就LeapArray这个类名来说,非常有我在开篇第二张图的那味道了。

在看LeapArray.currentWindow这个方法之前,先来看一个短小简单但是足够核心的一个方法LeapArray.calculateTimeIdx,整个方法只有两行代码,如下:

private int calculateTimeIdx(long timeMillis) {

// 将传入的当前时间按照窗口时长进行分段,拿到当前时间对应的分段ID

long timeId = timeMillis / windowLengthInMs;

// 将当前时间的分段段ID对应到窗口数组的下标ID上

return (int)(timeId % array.length());

}

上面的array定义如下:

protected final AtomicReferenceArray> array;

其赋值在构造方法中,如下语句:

this.array = new AtomicReferenceArray<>(sampleCount);

通过上面这个方法,我们就能够得到当前时间对应的窗口在窗口数组中的位置了,接下来我们要做的事情就是根据这个位置取出对应的窗口返回去给对应的统计逻辑使用。

直接看LeapArray.currentWindow[^为了方便阅读,精简了它的注释]方法定义:

public WindowWrap currentWindow(long timeMillis) {

if (timeMillis < 0) {

return null;

}

// 获取当前时间在窗口数组中映射的下标

int idx = calculateTimeIdx(timeMillis);

// 计算当前时间对应的窗口的开始时间,具体方法见下面

long windowStart = calculateWindowStart(timeMillis);

/*

* Get bucket item at given time from the array.

*

* (1) Bucket is absent, then just create a new bucket and CAS update to circular array.

* (2) Bucket is up-to-date, then just return the bucket.

* (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets.

*/

while (true) {

WindowWrap old = array.get(idx);

if (old == null) {

// 第一次进入,新建窗口,并使用cas的方式设置,如果出现争抢导致设置失败,暂时让出执行权待其它线程成功设置

WindowWrap window = new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));

if (array.compareAndSet(idx, null, window)) {

return window;

} else {

Thread.yield();

}

} else if (windowStart == old.windowStart()) {

// 当前时间对应的窗口开始时间等于获取到的窗口开始时间,那么当前获取到的窗口就是我们需要的

return old;

} else if (windowStart > old.windowStart()) {

// 当前时间对应的窗口开始时间大于获取到的窗口开始时间,那么当前获取到的窗口为已过期窗口,加锁重置

if (updateLock.tryLock()) {

try {

return resetWindowTo(old, windowStart);

} finally {

updateLock.unlock();

}

} else {

Thread.yield();

}

} else if (windowStart < old.windowStart()) {

// Should not go through here, as the provided time is already behind.

return new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));

}

}

}

LeapArray.calculateWindowStart方法:

protected long calculateWindowStart(long timeMillis) {

return timeMillis - timeMillis % windowLengthInMs;

}

总结上面的代码就是:先将当前时间按照统计时长分段,得到当前时间对应的分段ID。因为窗口数组是固定的,所以随着时间线向前发展,会不断的顺序循环使用数组中的窗口。所以使用当前时间对应的分段ID与窗口数组的长度求余得到当前时间对应的窗口在窗口数组中的下标,拿到这个下标后,接着就是在循环中获取这个下标对应的窗口了。

在获取指定下标对应的窗口时,要分情况进行处理:

如果对应下标窗口为null,那么就是第一次进入,创建新窗口并使用cas设置。如果非空走下面的逻辑。

如果获取到的窗口开始时间等于当前时间计算出来的对应窗口开始时间,那么就拿到了当前时间需要的窗口,直接返回。

如果获取到的窗口开始时间小于当前时间计算出来的对应窗口开始时间,那么就说明这个窗口已经过期了,所以加锁重置,然后重复使用。

当前时间小于旧的窗口的开始时间,理论上来说是不应该出现这种情况的,如果存在这种情况,那么返回一个无效的空窗口。

整个Sentinel滑动窗口算法的使用就上面这些代码,看完后第一感觉是代码如此简介,但是功能却如此高效强大。

打赏

微信扫一扫,打赏作者吧~

sentinel 时间窗口_Sentinel滑动窗口算法相关推荐

  1. Sentinel限流及其滑动窗口算法

    Sentinel限流及其滑动窗口算法 Sentinel的限流原理 滑动时间窗口算法 Sentinel的限流原理 限流效果,对应有DefaultController快速失败 WarmUpControll ...

  2. tcp协议头窗口,滑动窗口,流控制,拥塞控制关系

    tcp协议头窗口,滑动窗口,流控制,拥塞控制关系 参考文章 TCP 的那些事儿(下) http://coolshell.cn/articles/11609.html tcp/ip详解--拥塞控制 &a ...

  3. Flink 滚动窗口、滑动窗口详解

    1 滚动窗口(Tumbling Windows) 滚动窗口有固定的大小,是一种对数据进行"均匀切片"的划分方式.窗口之间没有重叠,也不会有间隔,是"首尾相接"的 ...

  4. TCP/IP卷一:63---TCP基础之(ARQ和重传、分组窗口和滑动窗口、流量控制和拥塞控制、设置重传超时)

    一.前言 到目前为止,我们一直在讨论那些自身不包含可靠传递数据机制的协议 它们可能会使用一种像校验和或CRC这样的数学函数来检测接收到的有差错的数据,但是它们不尝试去纠正差错 对于IP和UDP,根本没 ...

  5. tcp欢动窗口机制_TCP协议中的窗口机制------滑动窗口详解

    一.窗口机制的分类 在TCP协议当中窗口机制分为两种: 1.固定的窗口大小 2.滑动窗口 二.固定窗口存在的问题 如下图所示: 我们假设这个固定窗口的大小为1,也就是每次只能发送一个数据,只有接收方对 ...

  6. 限流的4种策略--固定窗口、滑动窗口、漏桶、令牌桶

    01 Why 分布式系统中,由于接口API无法控制上游调用方的行为,因此当瞬时请求量突增时,会导致服务器占用过多资源,发生响应速度降低.超时.乃至宕机,甚至引发雪崩造成整个系统不可用. 限流,Rate ...

  7. 【技术】TCP 的固定窗口和滑动窗口

    固定窗口 ● TCP 还提供了流量控制机制.流量控制可以调整给定会话中源和目的之间的 数据流速,有助于保持 TCP 传输的可靠性.流量控制的实施方法包括限制 一次可以转发的数据段数量,并要求在发送更多 ...

  8. tcp 协议中发送窗口的大小应该是_面试必备--TCP协议中的窗口机制滑动窗口详解...

    窗口机制分类 在TCP协议当中窗口机制分为两种: 1.固定的窗口大小 2.滑动窗口 固定窗口存在的问题 我们假设这个固定窗口的大小为1,也就是每次只能发送一个数据,只有接收方对这个数据进行了确认后才能 ...

  9. c语言退回N帧滑动窗口协议,滑动窗口协议实验的报告.docx

    滑动窗口协议实验的报告 滑动窗口协议实验报告 篇一:Exp1_滑动窗口协议_实验报告 Exp1 滑动窗口协议实验报告 [实验目标] ? 理解和掌握"滑动窗口"技术. ? 基于计算机 ...

最新文章

  1. Android Studio实用插件使用
  2. Java 文件和byte数组转换
  3. Python中is和==的区别
  4. 使用SQLQuery
  5. 近期GitHub上最热门的开源项目(附链接)
  6. codeforces 546A-C语言解题报告
  7. 就业阶段-java语言进价_day03
  8. 24点游戏详细截图介绍以及原型、Alpha、Beta对比
  9. 有关javaScript面向对象和原型笔记
  10. Spring中的ApplicationContextAware使用
  11. c语言bmp转换jpeg_PDF格式转换工具
  12. Cdn间隙性故障总结
  13. java补码运算代码_计算机原码、补码、反码与java移位运算符(//)
  14. java8新特性—— Lambda来由
  15. chromedriver与chrome各版本及下载地址
  16. QComboBox样式表
  17. 第一篇博客:A+B Problem
  18. 爬取华尔街日报的历史数据并翻译
  19. ASP.net 简单注册界面
  20. python空气质量提醒代码_空气质量指数查询示例代码

热门文章

  1. Django之爱鲜蜂项目开发 day05(二)
  2. 谈谈FrozenUI前端框架(应用心得) - 入坑篇
  3. 吕鑫MFC学习系列九
  4. flash咋导入歌曲html,使用html为flash页面添加音乐
  5. 塔望食品品牌策划:预制菜主要竞争品牌分析及研究
  6. 红外控制LED灯的亮灭———Arduino
  7. ESP32与掌控板IO接口编程入门 | ESP32轻松学(Arduino版)
  8. JMeter接口测试___参数化方法
  9. mac docker 修改 daemon.json 文件
  10. bolt数据库简单使用教程