Sentinel滑动窗口算法
在前面搞清楚了Sentinel的使用后,大致理了一下Sentinel的责任链,搞清楚了这个,基本就已经梳理清楚sentinel-core模块的大部分内容,顺着这条链路可以继续梳理很多东西。
知其然、知其所以然。而阅读源码就是最好的知其所以然的方式。这一次找了一些空闲时间,捋了一下它的滑动窗口算法,在这里做一个记录。后面会继续去梳理它的令牌算法和漏桶算法。
关于滑动窗口的原理,Sentinel为什么要使用滑动窗口,Sentinel是怎样使用的滑动,直接使用下面这两张图。一图胜千言,一张好的图足以说明问题,在这里我引用两张图。
首先从StatisticSlot类开始,它是Sentinel统计的核心功能槽,先看它的entry[^对这个方法做了一下精简,只保留了几行能够说明问题的代码。]方法:
@SpiOrder(-7000)
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@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<MetricBucket> 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<WindowWrap<T>> array;
其赋值在构造方法中,如下语句:
this.array = new AtomicReferenceArray<>(sampleCount);
通过上面这个方法,我们就能够得到当前时间对应的窗口在窗口数组中的位置了,接下来我们要做的事情就是根据这个位置取出对应的窗口返回去给对应的统计逻辑使用。
直接看LeapArray.currentWindow[^为了方便阅读,精简了它的注释]方法定义:
public WindowWrap<T> 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<T> old = array.get(idx);
if (old == null) {
// 第一次进入,新建窗口,并使用cas的方式设置,如果出现争抢导致设置失败,暂时让出执行权待其它线程成功设置
WindowWrap<T> window = new WindowWrap<T>(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<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
}
}
}
LeapArray.calculateWindowStart方法:
protected long calculateWindowStart(long timeMillis) {
return timeMillis - timeMillis % windowLengthInMs;
}
总结上面的代码就是:先将当前时间按照统计时长分段,得到当前时间对应的分段ID。因为窗口数组是固定的,所以随着时间线向前发展,会不断的顺序循环使用数组中的窗口。所以使用当前时间对应的分段ID与窗口数组的长度求余得到当前时间对应的窗口在窗口数组中的下标,拿到这个下标后,接着就是在循环中获取这个下标对应的窗口了。
在获取指定下标对应的窗口时,要分情况进行处理:
- 如果对应下标窗口为null,那么就是第一次进入,创建新窗口并使用cas设置。如果非空走下面的逻辑。
- 如果获取到的窗口开始时间等于当前时间计算出来的对应窗口开始时间,那么就拿到了当前时间需要的窗口,直接返回。
- 如果获取到的窗口开始时间小于当前时间计算出来的对应窗口开始时间,那么就说明这个窗口已经过期了,所以加锁重置,然后重复使用。
- 当前时间小于旧的窗口的开始时间,理论上来说是不应该出现这种情况的,如果存在这种情况,那么返回一个无效的空窗口。
整个Sentinel滑动窗口算法的使用就上面这些代码,看完后第一感觉是代码如此简介,但是功能却如此高效强大。
Sentinel滑动窗口算法的更多相关文章
- 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)
要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...
- sentinel 滑动窗口统计机制
sentinel的滑动窗口统计机制就是根据当前时间,获取对应的时间窗口,并更新该时间窗口中的各项统计指标(pass/block/rt等),这些指标被用来进行后续判断,比如限流.降级等:随着时间的推移, ...
- [DeeplearningAI笔记]卷积神经网络3.1-3.5目标定位/特征点检测/目标检测/滑动窗口的卷积神经网络实现/YOLO算法
4.3目标检测 觉得有用的话,欢迎一起讨论相互学习~Follow Me 3.1目标定位 对象定位localization和目标检测detection 判断图像中的对象是不是汽车--Image clas ...
- LC算法技巧总结(二):双指针和滑动窗口技巧
我把双指针技巧再分为两类,一类是「快慢指针」,一类是「左右指针」.前者解决主要解决链表中的问题,比如典型的判定链表中是否包含环:后者主要解决数组(或者字符串)中的问题,比如二分查找. 一.快慢指针的常 ...
- 7、滑动窗口套路算法框架——Go语言版
前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...
- 第二十六节,滑动窗口和 Bounding Box 预测
上节,我们学习了如何通过卷积网络实现滑动窗口对象检测算法,但效率很低.这节我们讲讲如何在卷积层上应用这个算法. 为了构建滑动窗口的卷积应用,首先要知道如何把神经网络的全连接层转化成卷积层.我们先讲解这 ...
- TCP之四:TCP 滑动窗口协议 详解
滑动窗口机制 滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口:同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口.发送窗口和接收窗口的序号的 ...
- 滑动窗口计数java实现
滑动窗口计数有很多使用场景,比如说限流防止系统雪崩.相比计数实现,滑动窗口实现会更加平滑,能自动消除毛刺. 概念上可以参考TCP的滑窗算法,可以看一下这篇文章(http://go12345.iteye ...
- [LeetCode] 76. 最小覆盖子串 ☆☆☆☆☆(滑动窗口)
https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-suan-fa-tong- ...
随机推荐
- iptables SNAT 和DNAT的转化配置实验
原文链接:http://www.jb51.net/LINUXjishu/402441.html DNAT(Destination Network Address Translation,目的地址转换) ...
- Django 的F查询与Q查询,事物
F查询 Django 提供 F() 来做这样的比较.F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值 示例1: 查询出卖出数大于库存数的商品 from ...
- 【NOIP2012模拟8.7】JZOJ2020年8月8日提高组T1 奶牛编号
[NOIP2012模拟8.7]JZOJ2020年8月8日提高组T1 奶牛编号 题目 作为一个神秘的电脑高手,Farmer John 用二进制数字标识他的奶牛. 然而,他有点迷信,标识奶牛用的二进制数字 ...
- 小米ICPC第一场自闭记
这次终于找到了靠谱队友,比之前我做不出来==队友做不出来好太多了 昨天3人热身赛疯狂杀了8道题,感觉今天稳了 一开始就瞅了A题,发现似乎可以dp,看了看数据,1e7,大概想出了nsqrtn算法,想着肯 ...
- 第4章 基础知识进阶 第4.1节 Python基础概念之迭代、可迭代对象、迭代器
第四章 基础知识进阶第十七节 迭代.可迭代对象.迭代器 一. 引言 本来计划讲完元组和字典后就讲列表解析和字典解析,但要理解列表解析和字典解析,就需要掌握Python的高级的类型迭代器,因此本节 ...
- 老猿学5G扫盲贴:PDU协议数据单元、PDU连接业务和PDU会话的功能详解
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.PDU 关于PDU在百度百科是这样定义的:协议 ...
- PyQt(Python+Qt)学习随笔:QDockWidget停靠部件的setWidget和widget方法
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 QDockWidget对象由一个标题栏和内容区域组成.QDockWid ...
- scrapy爬虫登录edusrc查看漏洞列表
scrapy登录界面的难点在于登录时候的验证码,我们通过使用scrapy.FormRequest向目标网站提交数据(表单提交),同时将验证码显示在本地,手动输入,进而登录. 验证码是类似于这种的,才可 ...
- P4085 [USACO17DEC]Haybale Feast
我又开始水了,感觉又是一道虚假的蓝题 题意 非常好理解,自己看吧 题解 可以比较轻易的发现,如果对于一段满足和大于等于 \(m\) 的区间和其满足和大于等于 \(m\) 的子区间来说,选择子区间肯定是 ...
- Springboot websocket学习Demo
使用的是springboot2.1.4版本 <parent> <groupId>org.springframework.boot</groupId> <art ...