当前位置: 首页 > news >正文

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张图带你彻底搞懂限流、熔断、服务降级

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

服务限流详解

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

接口限流算法总结

相关文章:

sentinel中StatisticSlot数据采集的原理

StatisticSlot数据采集的原理 时间窗口 固定窗口 在固定的时间窗口内&#xff0c;可以允许固定数量的请求进入&#xff1b;超过数量就拒绝或者排队&#xff0c;等下一个时间段进入, 如下图 时间窗长度划分为1秒 单个时间窗的请求阈值为3 上述存在一个问题, 假如9:18:04:…...

图像去噪与增强技术

图像去噪与增强技术是数字图像处理领域中的两个重要方面&#xff0c;它们分别关注消除图像中的噪声和改善图像的质量。 图像去噪技术的主要目的是从受噪声干扰的图像中去除不必要的随机信号&#xff0c;以恢复图像的真实内容。这对于图像的进一步分析和理解至关重要。去噪技术包…...

SpringJPA 做分页条件查询

前言: 相信小伙伴们的项目很多都用到SpringJPA框架的吧,对于单表的增删改查利用jpa是很方便的,但是对于条件查询并且分页 是不是很多小伙伴不经常写到. 今天我整理了一下在这里分享一下. 话不多说直接上代码: Controller: RestController public class ProductInstanceContr…...

[Java基础揉碎]单例模式

目录 什么是设计模式 什么是单例模式 饿汉式与懒汉式 饿汉式vs懒汉式 懒汉式存在线程安全问题 什么是设计模式 1.静态方法和属性的经典使用 2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。设计模式就像是经典的棋谱&am…...

unity无法使用道路生成插件Road Architect(ctrl和shift无法标点)

切换一下布局就行了。 附&#xff1a;Road Architect教学地址...

Django下载使用、文件介绍

【一】下载并使用 【1】下载框架 &#xff08;1&#xff09;注意事项 计算机名称不要出现中文python解释器版本不同可能会出现启动报错项目中所有的文件名称不要出现中文多个项目文件尽量不要嵌套,做到一项一夹 &#xff08;2&#xff09;下载 Django属于第三方模块&#…...

Docker进阶:Docker-cpmpose 实现服务弹性伸缩

Docker进阶&#xff1a;Docker-cpmpose 实现服务弹性伸缩 一、Docker Compose基础概念1.1 Docker Compose简介1.2 Docker Compose文件结构 二、弹性伸缩的原理和实现步骤2.1 弹性伸缩原理2.2 实现步骤 三、技术实践案例3.1 场景描述3.2 配置Docker Compose文件3.3 使用 docker-…...

opencv各个模块介绍(2)

Features2D 模块&#xff1a;特征检测和描述子计算模块&#xff0c;包括SIFT、SURF等算法。 Features2D 模块提供了许多用于特征检测和描述子匹配的函数和类&#xff0c;这些函数和类可用于图像特征的提取、匹配和跟踪。 FeatureDetector&#xff1a;特征检测器的基类&#xf…...

HTTPS:原理、使用方法及安全威胁

文章目录 一、HTTPS技术原理1.1 主要技术原理1.2 HTTPS的工作过程1.2.1 握手阶段1.2.2 数据传输阶段 1.3 HTTPS的安全性 二、HTTPS使用方法三、HTTPS安全威胁四、总结 HTTPS&#xff08;全称&#xff1a;Hyper Text Transfer Protocol over Secure Socket Layer&#xff09;&am…...

【云开发笔记No.6】腾讯CODING平台

腾讯云很酷的一个应用&#xff0c;现在对于研发一体化&#xff0c;全流程管理&#xff0c;各种工具层出不穷。 云时代用云原生&#xff0c;再加上AI&#xff0c;编码方式真是发生了质的变化。 从前&#xff0c;一个人可以写一个很酷的软件&#xff0c;后来&#xff0c;这变得…...

20.Ubuntu下安装GCC

文章目录 Ubuntu下安装GCC查看官方安装指导错误缺少gmp库缺少32位开发库libcg: error: gengtype-lex.c: No such file or directoryreference 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; Ubuntu下安装GCC 为了支持新的c标准&#xff…...

2.windows ubuntu子系统配置

打开UBuntu后&#xff0c; > wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh #下载conda软件。 > bash Miniconda3-latest-Linux-x86_64.sh #下载完conda后执行这步 > source ~/.bashrc > conda-h #出现一下…...

vscode的一些技巧

技巧1&#xff1a;调试时传参数 在launch.json的configuration中"pwd"或者"program"选项之后添加如下选项&#xff1a; “--args”:["参数1", "参数2", ..., "参数3] 参数之间使用逗号隔开 技巧2&#xff1a;断点 普通断点使…...

JavaEE企业级分布式高级架构师课程

教程介绍 本课程主要面向1-5年及以上工作经验的Java工程师&#xff0c;大纲由IT界知名大牛 — 廖雪峰老师亲自打造&#xff0c;由来自一线大型互联网公司架构师、技术总监授课&#xff0c;内容涵盖深入spring5设计模式/高级web MVC开发/高级数据库设计与开发/高级响应式web开发…...

c语言函数大全(K开头)

c语言函数大全(K开头) There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated should leave quickly. 函数名…...

高效备考2025年AMC8竞赛:吃透2000-2024年600道真题(免费送题)

我们继续来随机看五道AMC8的真题和解析&#xff0c;根据实践经验&#xff0c;对于想了解或者加AMC8美国数学竞赛的考生来说&#xff0c;吃透AMC8历年真题是备考更加科学、有效的方法之一。 最近有家长朋友问我&#xff0c;现在有哪些类似于奥数的比赛可以参加&#xff1f;我的…...

HTML5和CSS3新特性

Html新增属性 1.新增语义化标签 <header>&#xff1a;头部标签 <nav>&#xff1a;导航标签 <article>&#xff1a;内容标签 <section>&#xff1a;定义文档某个区域 <aside>&#xff1a;侧边栏标签 <footer>&#xff1a;尾部标签 2.…...

Java基础-正则表达式

文章目录 1.基本介绍2.正则底层实现1.matcher.find()完成的任务2.matcher.group(0)分析1.源代码2.解释&#xff08;不分组&#xff09;3.解释&#xff08;分组&#xff09; 3.总结 3.正则表达式语法1.基本介绍2.元字符的转义符号1.基本介绍2.代码实例 3.字符匹配符1.基本介绍2.…...

构建Pytorch虚拟环境教程

构建PyTorch虚拟环境通常涉及使用诸如Anaconda或venv等工具来管理Python环境&#xff0c;以便在一个独立的空间中安装PyTorch和其他依赖项。以下是使用Anaconda创建PyTorch虚拟环境的步骤&#xff08;适用于不同操作系统&#xff0c;包括Windows、Linux和MacOS&#xff09;&…...

uniapp页面嵌套其他页面的实现

功能: 类似于一个drawer&#xff0c;当主页面加载的时候会一并加载url对应的组件&#xff0c;当点击后以drawer形式显示组件里面的内容&#xff0c;可动画。 <navigator url"/pages/my/components/personalMessage" slot"right"><view><di…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

手机平板能效生态设计指令EU 2023/1670标准解读

手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读&#xff0c;综合法规核心要求、最新修正及企业合规要点&#xff1a; 一、法规背景与目标 生效与强制时间 发布于2023年8月31日&#xff08;OJ公报&…...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...