sentinel中StatisticSlot数据采集的原理

StatisticSlot数据采集的原理

时间窗口

固定窗口

在固定的时间窗口内,可以允许固定数量的请求进入;超过数量就拒绝或者排队,等下一个时间段进入, 如下图

  • 时间窗长度划分为1秒

  • 单个时间窗的请求阈值为3
    在这里插入图片描述

上述存在一个问题, 假如9:18:04:333-9:18:05:000产生了2个请求, 9:18:05:000-9:18:05:333产生了3个请求, 那么也就是说9:18:04:333-9:18:05:333这一秒内产生5个请求, 正常来说这里已经超出了阈值
在这里插入图片描述

但是由于是固定窗口, 也就是这里只能9:18:04:000-9:18:05:000, 9:18:05:000-9:18:06:000这样子去处理, 所以实际上打达不到我们的要求的

滑动窗口

滑动窗口诞生的原因就在于解决固定窗口那个致命的问题,为什么我说固定窗口的问题是致命的?因为我们系统限流的目的是要在任意时间都能应对突然的流量暴增,也就是说我的系统最大在1s内能够处理请求3,但如果使用固定窗口的算法,就会造成在9:18:04:333-9:18:05:333之间的请求无法限流,从而严重的话会导致服务雪崩

滑动窗口如下图

  • 时间窗长度划分为1秒, 并且是滑动的

  • 单个时间窗的请求阈值为3
    在这里插入图片描述

如果要判断请求是否能正常通过, 那么就要把当前时间点作为终点, 统计前1秒内的请求数, 判断请求数是否达到阈值, 如果没有达到阈值就放行, 如果达到阈值了就通过

上边的问题是是解决了, 但是存在一些性能问题, 假设请求落在9:18:05:333, 往前移动1s距离, 那么就是以9:18:04:333作为起点, 统计9:18:04:333-9:18:05:333之间的请求数, 当请求落在9:18:05:633, 那么就要统计9:18:04:633-9:18:05:633之间的请求数, 发现9:18:04:633-9:18:05:333之间的数据上一次的时候就出现过了, 但是这里又得重新统计, 也就说每移动一次窗口, 那么都要重新统计重复区域的请求数量, 从而导致浪费大量系统资源, 如下图, 黄色区域为重复统计区域
在这里插入图片描述

出现了问题, 就要解决

我们需要引入更加细粒度化的计算, 也就是说需要增加子时间窗口, 那么这里引入的子时间窗口, 我们称为样本窗口

  1. 样本窗口的长度必须小于滑动窗口长度,因为如果样本窗口等于滑动窗口长度的话,就和固定窗口没啥区别了
  2. 通常情况下滑动窗口的长度是样本窗口的整数倍,比如:10 * 样本窗口 = 1 个滑动窗口
  3. 每个样本窗口在到达终点时间时,会统计本样本窗口中的流量数据并且记录下来,用于复用
  4. 当一个请求到达时,系统会首先统计当前请求时间点所在的样本窗口内的流量数据。接着,系统会检查在当前请求时间点之前的滑动窗口中的样本窗口,将它们的统计数据进行求和。如果这个求和值没有超出事先设定的阈值,请求将会被允许通过。然而,如果求和值超过了阈值,系统会触发限流措施,拒绝该请求的访问

假设

  • 时间窗长度为1s
  • 一个时间窗内包含三个样本窗口
  • 阈值为30

那么10:00:00:000-10:00:01:000内请求数为10 + 5 + 10 = 25 < 30, 所以大胆放行
在这里插入图片描述

那么10:00:00:333-10:00:01:333内请求数为5 + 10 + 7= 22 < 30, 继续放行
在这里插入图片描述

那么10:00:00:666-10:00:01:666内请求数为10 + 7 + 30= 47 > 30, 这里就要限流了

在这里插入图片描述

限流
在这里插入图片描述

那么10:00:01:000-10:00:02:000内请求数为7 + 30 + 7= 40 > 30, 这里就要限流了
在这里插入图片描述

那么10:00:01:333-10:00:02:333内请求数为30 + 7 + 34 = 71 > 30, 这里就要限流了
在这里插入图片描述

ps: 样本窗口的数量影响着滑动窗口算法的精度,依然有时间片的概念,无法根本解决临界点问题

数据统计

底层数据结构

StatisticSlot.entry() 中的 node.addPassRequest(count)方法

public void addPassRequest(int count) {// 增加当前入口的DefaultNode中的数据super.addPassRequest(count);// 增加当前资源的 ClusterNode 中的全局统计数据this.clusterNode.addPassRequest(count);
}@Override
public void addPassRequest(int count) {// 为滑动计数器增加本次请求rollingCounterInSecond.addPass(count);rollingCounterInMinute.addPass(count);
}

rollingCounterInSecond 就是一个真正保存数据的计量器,数据类型为 ArrayMetric,也就是说 Sentinel 在统计数据上采取的是一个名为 ArrayMetric 的 Java 类,如下

// 定义了一个使用数组保存数据的计量器,样本窗口数量为2(SAMPLE_COUNT),时间窗口长度为1000ms(INTERVAL)
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL);

那么 ArrayMetric 类又是如何存储数据的呢?

// 这是一个使用数组保存数据的计量器类,数据就保存在data中
public class ArrayMetric implements Metric {// 真正存储数据的地方private final LeapArray<MetricBucket> data;
}public abstract class LeapArray<T> {// 样本窗口的长度protected int windowLengthInMs;// 一个时间窗口包含的样本窗口数量,公式 intervalInMs / windowLengthInMs,也就是时间窗口长度 / 样本窗口长度protected int sampleCount;// 时间窗口长度protected int intervalInMs;// 也是时间窗口长度,只是单位为sprivate double intervalInSecond;// WindowWrap : 样本窗口类// 这是一个数组// 这里的泛型T实际类型为 MetricBucketprotected final AtomicReferenceArray<WindowWrap<T>> array;
}

LeapArray 类似于一个样本窗口管理类,而真正的样本窗口类是 WindowWrap<T>,对于样本窗口的概念我们肯定不陌生了,其包含:单个样本窗口的长度、样本窗口的开始时间戳,如下所示:

// 样本窗口类,泛型T为MetricBucket
public class WindowWrap<T> {// 单个样本窗口的长度private final long windowLengthInMs;// 样本窗口的起始时间戳private long windowStart;// 当前样本窗口的统计数据,类型为 MetricBucketprivate T value;
}

关于泛型真实类型为 MetricBucket 也很简单,可以从前面代码 ArrayMetric#LeapArray<MetricBucket> 得出。接下来就看真实类型 MetricBucket 是个什么东西

// 统计数据的封装类
public class MetricBucket {// 统计的数据真实存放在LongAdder里// 但是为什么要数组?直接用LongAdder+1不就行了?因为统计的数据是多维度的,这些维度类型在MetricEvent枚举中。private final LongAdder[] counters;private volatile long minRt;
}

这里有一个巧妙的设计,就是为什么用LongAdder[]而不是用LongAdder,正是因为统计的数据是多维度的,比如:统计通过的 QPS、统计失败的 QPS 等,因此设计成数组,我们就可以将不同类型放到不同的数组下标里

关系如下图
在这里插入图片描述

addPass()方法

@Override
public void addPassRequest(int count) {// 为滑动计数器增加本次请求rollingCounterInSecond.addPass(count);rollingCounterInMinute.addPass(count);
}public void addPass(int count) {// 获取当前时间点所在的样本窗口WindowWrap<MetricBucket> wrap = data.currentWindow();// 将当前请求的计数量添加到当前样本窗口的统计数据中wrap.value().addPass(count);
}

其主要分为三部分:

  1. 当前时间所在的样本窗口如果还没创建,则需要初始化。
  2. 若当前样本窗口的起始时间与计算出的样本窗口起始时间相同,则说明这两个是同一个样本窗口,直接获取就行。
  3. 若当前样本窗口的起始时间大于计算出的样本窗口起始时间,说明计算出来的样本窗口已经过时了,需要将原来的样本窗口替换为新的样本窗口。数组的环形数组,不是无限长的,比如存 1s,1000 个样本窗口,那么下 1s 的 1000 个时间窗口会覆盖上一秒的。
  4. 若当前样本窗口的起始时间小于计算出的样本窗口起始时间,一般不会出现,因为时间不会倒流,除非人为修改系统时间导致时钟回拨
public WindowWrap<T> currentWindow(long timeMillis) {if (timeMillis < 0) {return null;}// 计算当前时间所在的样本窗口index,也就是样本窗口的下标,即在计算数组LeapArray中的下标int idx = calculateTimeIdx(timeMillis);// Calculate current bucket start time.// 计算当前样本窗口的开始时间点long windowStart = calculateWindowStart(timeMillis);while (true) {// 获取到当前时间所在的样本窗口WindowWrap<T> old = array.get(idx);// 代表当前时间所在的样本窗口没有,需要创建if (old == null) {/**     B0       B1      B2    NULL      B4* ||_______|_______|_______|_______|_______||___* 200     400     600     800     1000    1200  timestamp*                             ^*                          time=888*            		bucket为空, 所以新建并更新** 如果旧的bucket不存在,那么我们在windowStart创建一个新的bucket,然后尝试通过CAS操作* 更新循环数组。只有一个线程可以成功更新,而其他线程争夺这个时间片*/// 创建一个时间窗口WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));// CAS 将新建窗口放入LeapArrayif (array.compareAndSet(idx, null, window)) {// 更新成功,返回创建的bucketreturn window;} else {// 获取锁失败, 线程将放弃其时间片以等待可用的bucketThread.yield();}} else if (windowStart == old.windowStart()) { // 若当前样本窗口的起始时间与计算出的样本窗口起始时间相同,则说明这两个是同一个样本窗口,直接获取就行/**     B0       B1      B2     B3      B4* ||_______|_______|_______|_______|_______||___* 200     400     600     800     1000    1200  timestamp*                             ^*                          time=888*            桶3的起始时间: 800,所以它是最新的** 如果当前windowStart等于旧bucket的开始时间戳,则表示时间在bucket内,因此直接返回bucket*/return old;} else if (windowStart > old.windowStart()) { // 若当前样本窗口的起始时间大于计算出的样本窗口起始时间。说明计算出来的样本窗口已经过时了,需要将原来的样本窗口替换为新的样本窗口。 数组的环形数组,不是无限长的,比如存1s,1000个样本窗口,那么下1s的1000个时间窗口会覆盖上一秒的/**   (old)*             B0       B1      B2    NULL      B4* |_______||_______|_______|_______|_______|_______||___* ...    1200     1400    1600    1800    2000    2200  timestamp*                              ^*                           time=1676*          Bucket 2的startTime: 400,已弃用,应重置** 如果旧bucket的开始时间戳落后于当前时间,则表示该bucket已弃用。我们必须将bucket重置为当前的windowStart。请注意,重置和清理操作很难是原子的,所以我们需要一个更新锁来保证bucket更新的正确性** 更新锁是有条件的(小范围),只有当桶被弃用时才会生效,所以在大多数情况下它不会导致性能损失*/if (updateLock.tryLock()) {try {// 成功获取更新锁,现在我们重置bucket// 替换老的样本窗口return resetWindowTo(old, windowStart);} finally {updateLock.unlock();}} else {// 获取锁失败,线程将放弃其时间片以等待可用的bucketThread.yield();}} else if (windowStart < old.windowStart()) { // 若当前样本窗口的起始时间小于计算出的样本窗口起始时间,一般不会出现,因为时间不会倒流,除非人为修改系统时间(即时钟回拨)return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));}}
}

流程图如下
在这里插入图片描述

上述使用到的数组是一个环形数组, 不是我们所谓的普通数组, 目地就是复用, 节省内存

环形数组的工作原理

现有环形数组, 长度为8, 目前已经用了6个位置
在这里插入图片描述

继续添加元素

在这里插入图片描述

继续添加元素
在这里插入图片描述

目前元素已经满了, 接着添加, 发现它把原来存在元素1的位置替换了, 换成了9
在这里插入图片描述

继续添加, 同理可得, 那么元素应该就应该替换到元素2这个位置
在这里插入图片描述

上边就是环形数组的工作原理

currentWindow的图文分析
old == null

这个表示环形数组都没有, 那么就创建一个环形数组, 并将元素设置进去, 这里就不画图了

windowStart > old.windowStart()

在这里插入图片描述

windowStart == old.windowStart()

在这里插入图片描述

windowStart < old.windowStart()

在这里插入图片描述

参考资料

通关 Sentinel 流量治理框架 - 编程界的小學生

10张图带你彻底搞懂限流、熔断、服务降级

分布式服务限流实战,已经为你排好坑了

服务限流详解

新来个技术总监,把限流实现的那叫一个优雅,佩服!

接口限流算法总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767255.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年C语言最新经典面试题汇总(11-20)

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

【LLM多模态】Cogvlm图生文模型结构和训练流程

note Cogvlm的亮点&#xff1a; 当前主流的浅层对齐方法不佳在于视觉和语言信息之间缺乏深度融合&#xff0c;而cogvlm在attention和FFN layers引入一个可训练的视觉专家模块&#xff0c;将图像特征与文本特征分别处理&#xff0c;并在每一层中使用新的QKV矩阵和MLP层。通过引…

01. 如何配置ESP32环境?如何开发ESP32?

0. 前言 此文章收录于《ESP32学习笔记》专栏&#xff0c;此专栏会结合实际项目记录作者学习ESP32的过程&#xff0c;争取每篇文章能够将细节讲明白&#xff0c;会应用。 1. 安装IDE&#xff1a;Thonny 后续项目中我们都是使用pythont语言&#xff0c;而thonny工具能很好的支撑E…

DFT应用:频谱分辨率和频率估计

目录 一、什么是频谱分辨率 1. 关于矩形窗函数 2. 分析余弦信号频谱 3. 频谱分辨率的定义 4. 如何提高频谱分辨率 二、关于频率估计 一、什么是频谱分辨率 1. 关于矩形窗函数 当k1时&#xff0c;主瓣宽度就是2x&#xff0c;能量主要集中在主瓣部分&#xff0c;频谱泄露…

【C语言数据结构】排序

1.排序的概念 在深入研究各个排序算法之前&#xff0c;首先&#xff0c;我们要对排序有个大概的了解&#xff0c;即与排序相关的一些概念 Q&#xff1a;什么是排序&#xff1f; A&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小…

如何实现跨标签页通讯

什么是跨标签页通讯 同一浏览器&#xff0c;可以打开多个标签页&#xff0c;跨标签页通讯就是&#xff0c;一个标签页能够发消息给另一标签页。 有哪些实现方案 localStorage &#xff08;window.onstorage事件监听&#xff09;BroadcastChannel&#xff08;广播&#xff09…

C语言经典算法-9

文章目录 其他经典例题跳转链接46.稀疏矩阵47.多维矩阵转一维矩阵48.上三角、下三角、对称矩阵49.奇数魔方阵50.4N 魔方阵51.2(2N1) 魔方阵 其他经典例题跳转链接 C语言经典算法-1 1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠走迷官&#xff08;一&#xff09;6.…

pytest框架的封装以及用例管理框架

pytest框架的封装以及用例管理框架 公共类统一封装requests_util02.pytest_api01.py 自动化测试的基础自动化测试的介入点自动化测试和手工测试占比自动化实施过程 pytest元素定位元素定位查找元素定位的方式通过 ID 定位通过 Name 定位通过 Class Name 定位通过 Tag Name 定位…

唯众物联网安装调试员实训平台物联网一体化教学实训室项目交付山东技师学院

近日&#xff0c;山东技师学院物联网安装调试员实训平台及物联网一体化教学实训室采购项目已顺利完成交付并投入使用&#xff0c;标志着学院在物联网技术教学与实践应用方面迈出了坚实的一步。 山东技师学院作为国内知名的技师培养摇篮&#xff0c;一直以来致力于为社会培养高…

windows11 openssh服务开启;第三方ping不通局域网windows电脑;ssh连接内部ubuntu系统

参考&#xff1a;https://blog.csdn.net/2301_77554343/article/details/134328867 1、windows11 openssh开启 1&#xff09;我这边可选功能在设置-系统里面&#xff1b;其他网上看在应用下&#xff1b;添加可选openssh服务器安装 2&#xff09;安装后打开&#xff0c;管理员…

光伏户用开发技巧

一、开发户用光伏的技巧有哪些&#xff1f; 1.项目可行性分析 电站开发前需要先进行可行性分析&#xff0c;从当地的气象条件、电网的接入能力、政策环境等方便分析。可以自行收集数据分析&#xff0c;也可以邀请专业机构进行评估。 2.选址和电站设计 光伏电站的选址&#…

agent利用知识来做规划:《KnowAgent: Knowledge-Augmented Planning for LLM-Based Agents》笔记

文章目录 简介KnowAgent思路准备知识Action Knowledge的定义Planning Path Generation with Action KnowledgePlanning Path Refinement via Knowledgeable Self-LearningKnowAgent的实验结果 总结参考资料 简介 《KnowAgent: Knowledge-Augmented Planning for LLM-Based Age…

Spring Cloud四:微服务治理与安全

Spring Cloud一&#xff1a;Spring Cloud 简介 Spring Cloud二&#xff1a;核心组件解析 Spring Cloud三&#xff1a;API网关深入探索与实战应用 文章目录 一、服务注册中心的选型与最佳实践1. 主流服务注册中心概述2. 最佳实践建议(1)、选型建议(2)、高可用性与稳定性1). 高可…

北京密云广电许可证办理要求与流程

北京密云广电许可证办理攻略&#xff1a;要求与流程全解析 一、引言 各位老板好&#xff0c;我是经典世纪胡云帅&#xff0c;随着广播电视行业的快速发展&#xff0c;越来越多的企业和个人希望进入这一领域&#xff0c;开展节目制作、传播等业务。而在北京密云&#xff0c;这一…

二进制王国(蓝桥杯备赛)【sort/cmp的灵活应用】

二进制王国 题目链接 https://www.lanqiao.cn/problems/17035/learning/?contest_id177 题目描述 思路 这里就要灵活理解字典序排列&#xff0c;虽然string内置可以直接比较字符串字典序&#xff0c;但是在拼接时比较特殊&#xff0c;比如 11的字典序小于110&#xff0c;但…

HTTP --- 下

目录 1. HTTP请求方式 1.1. HTML 表单 1.2. GET && POST方法 1.2.1. 用 GET 方法提交表单数据 1.2.2. 用 POST 方法提交表单数据 1.2.3. 总结 1.3. 其他方法 2. HTTP的状态码 2.1. 重定向 2.1.1. 临时重定向 && 永久重定向 2.1.2. 302 &&…

【MySQL系列】Public Key Retrieval is not allowed

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Python灰帽子网络安全实践

教程介绍 旨在降低网络防范黑客的入门门槛&#xff0c;适合所有中小企业和传统企业。罗列常见的攻击手段和防范方法&#xff0c;让网站管理人员都具备基本的保护能力。Python 编程的简单实现&#xff0c;让网络运维变得更简单。各种黑客工具的理论和原理解剖&#xff0c;让人知…

基于Matlab的眼底图像血管分割,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

车道线检测论文:《Ultra Fast Structure-aware Deep Lane Detection》

该论文标题为《Ultra Fast Structure-aware Deep Lane Detection》&#xff0c;作者是浙江大学计算机科学与技术学院的Zequn Qin、Huanyu Wang和Xi Li。论文提出了一种新颖的、简单而有效的车道检测方法&#xff0c;旨在解决具有挑战性场景下的车道检测问题&#xff0c;并实现极…