原创不易,转载请注明出处

文章目录

  • 前言
  • 1.sentinel滑动窗口实现原理
  • 2.sentinel使用滑动窗口都统计啥
  • 3.滑动窗口源码实现
    • 3.1 MetricBucket
    • 3.2 WindowWrap
    • 3.3 LeapArray

前言

本文主要是介绍下sentinel指标收集统计的一个重要基础组件那就是滑动窗口,将分为三个小节介绍,首先是大体介绍下sentinel滑动窗口的基本实现原理,心里先有个大概的印象,然后我们在看下sentinel使用这个滑动窗口用来统计哪些指标,最后就是进行源码剖析,看看代码是怎样实现,怎样写的。

1.sentinel滑动窗口实现原理

这里我们先介绍下这个sentinel中的滑动窗口是怎么回事,然后在说下它是怎么实现的。
滑动窗口可以先拆为滑动跟窗口两个词,先介绍下窗口,你可以这么理解,一段是时间就是窗口,比如说我们可以把这个1s认为是1个窗口
这个样子我们就能将1分钟就可以划分成60个窗口了,这个没毛病吧。如下图我们就分成了60个窗口(这个多了我们就画5个表示一下)

比如现在处于第1秒上,那1s那个窗口就是当前窗口,就如下图中红框表示。

好了,窗口就介绍完了,现在在来看下滑动,滑动很简单,比如说现在时间由第1秒变成了第2秒,就是从当前这个窗口---->下一个窗口就可以了,这个时候下一个窗口就变成了当前窗口,之前那个当前窗口就变成了上一个窗口,这个过程其实就是滑动。

好了,介绍完了滑动窗口,我们再来介绍下这个sentinel的滑动窗口的实现原理。
其实你要是理解了上面这个滑动窗口的意思,sentinel实现原理就简单了。
先是介绍下窗口中里面都存储些啥。也就是上面这个小框框都有啥。

  1. 它得有个开始时间吧,不然你怎么知道这个窗口是什么时候开始的
  2. 还得有个窗口的长度吧,不然你咋知道窗口啥时候结束,通过这个开始时间+窗口长度=窗口结束时间,就比如说上面的1s,间隔1s
  3. 最后就是要在这个窗口里面统计的东西,你总不能白搞些窗口,搞些滑动吧。所以这里就存储了一堆要统计的指标(qps,rt等等)

说完了这一个小窗口里面的东西,就得来说说是怎么划分这个小窗口,怎么管理这些小窗口的了,也就是我们的视野得往上提高一下了,不能总聚在这个小窗口上。

  1. 要知道有多少个小窗口,在sentinel中也就是sampleCount,比如说我们有60个窗口。
  2. 还有就是intervalInMs,这个intervalInMs是用来计算这个窗口长度的,intervalInMs/窗口数量= 窗口长度。也就是我给你1分钟,你给我分成60个窗口,这个时候窗口长度就是1s了,那如果我给你1s,你给我分2个窗口,这个时候窗口长度就是500毫秒了,这个1分钟,就是intervalInMs。
  3. 再就是存储这个窗口的容器(这里是数组),毕竟那么多窗口,还得提供计算当前时间窗口的方法等等

最后我们就来看看这个当前时间窗口是怎么计算的。
咱们就拿 60个窗口,这个60个窗口放在数组中,窗口长度是1s 来计算,看看当前时间戳的一个时间窗口是是在数组中哪个位置。比如说当前时间戳是1609085401454 ms
算出秒 = 1609085401454 /1000(窗口长度)
在数组的位置 = 算出秒 %数组长度

我们再来计算下 某个时间戳对应窗口的起始时间,还是以1609085401454 来计算
窗口startTime = 1609085401454 - 1609085401454%1000(窗口长度)
这里1609085401454%1000(窗口长度) 能算出来它的毫秒值,也就是454 , 减去这个后就变成了1609085401000

好了,sentinel 滑动窗口原理就介绍完成了。

2.sentinel使用滑动窗口都统计啥

我们来介绍下使用这个滑动窗口都来统计啥

public enum MetricEvent {/*** Normal pass.*/PASS,// 通过/*** Normal block.*/BLOCK,// 拒绝的EXCEPTION,// 异常SUCCESS,//成功RT,// 耗时/*** Passed in future quota (pre-occupied, since 1.5.0).*/OCCUPIED_PASS
}

这是最基本的指标,然后通过这些指标,又可以计算出来比如说最大,最小,平均等等的一些指标。

3.滑动窗口源码实现

我们先来看下这个窗口里面的统计指标的实现 MetricBucket

3.1 MetricBucket

这个MetricBucket 是由LongAdder数组组成的,一个LongAdder就是一个MetricEvent ,也就是第二小节里面的PASS ,BLOCK等等。
我们稍微看下就可以了

可以看到它在实例化的时候创建一个LongAdder 数据,个数就是那堆event的数量。这个LongAdder 是jdk8里面的原子操作类,你可以把它简单认为AtomicLong。然后下面就是一堆get 跟add 的方法了,这里我们就不看了。
接下来再来看看那这个窗口的实现WindowWrap类

3.2 WindowWrap

先来看下这个成员

窗口长度,窗口startTime ,指标统计的 都有了,下面的就没啥好看的了,我们再来看下的一个方法吧

就是判断某个时间戳是不是在这个窗口中
时间戳要大于等于窗口开始时间 && 小于这个结束时间。

接下来再来看下这个管理窗口的类LeapArray

3.3 LeapArray


看下它的成员, 窗口长度, 样本数sampleCount 也就是窗口个数, intervalInMs ,再就是窗口数组
看看它的构造方法

这个构造方法其实就是计算出来这个窗口长度,创建了窗口数组。
再来看下一个很重要的方法

 /*** Get bucket item at provided timestamp.*** 获取某个时间的窗口* @param timeMillis a valid timestamp in milliseconds* @return current bucket item at provided timestamp if the time is valid; null if time is invalid*/public WindowWrap<T> currentWindow(long timeMillis) {if (timeMillis < 0) {return null;}// 计算出窗口在哪个 元素位置处int idx = calculateTimeIdx(timeMillis);// 计算当前时间的bucket 的开始时间// Calculate current bucket start time.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) {/**     B0       B1      B2    NULL      B4* ||_______|_______|_______|_______|_______||___* 200     400     600     800     1000    1200  timestamp*                             ^*                          time=888*            bucket is empty, so create new and update** If the old bucket is absent, then we create a new bucket at {@code windowStart},* then try to update circular array via a CAS operation. Only one thread can* succeed to update, while other threads yield its time slice.*/// 创建窗口WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));if (array.compareAndSet(idx, null, window)) {// Successfully updated, return the created bucket./// 设置成功,就返回创建的bucketreturn window;} else {// Contention failed, the thread will yield its time slice to wait for bucket available.//线程将提供它的时间片来等待桶可用Thread.yield();}// 老得窗口起始时间 与 现在的窗口起始时间一致,说明这个是一个窗口} else if (windowStart == old.windowStart()) {/**     B0       B1      B2     B3      B4* ||_______|_______|_______|_______|_______||___* 200     400     600     800     1000    1200  timestamp*                             ^*                          time=888*            startTime of Bucket 3: 800, so it's up-to-date** If current {@code windowStart} is equal to the start timestamp of old bucket,* that means the time is within the bucket, so directly return the bucket.*/return old;// 现在的窗口时间 大于之前的窗口起始时间} else if (windowStart > old.windowStart()) {/**   (old)*             B0       B1      B2    NULL      B4* |_______||_______|_______|_______|_______|_______||___* ...    1200     1400    1600    1800    2000    2200  timestamp*                              ^*                           time=1676*          startTime of Bucket 2: 400, deprecated, should be reset** If the start timestamp of old bucket is behind provided time, that means* the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.* Note that the reset and clean-up operations are hard to be atomic,* so we need a update lock to guarantee the correctness of bucket update.** The update lock is conditional (tiny scope) and will take effect only when* bucket is deprecated, so in most cases it won't lead to performance loss.*//// 获取锁if (updateLock.tryLock()) {try {// Successfully get the update lock, now we reset the bucket.// 重置这个窗口 并返回return resetWindowTo(old, windowStart);} finally {/// 释放锁updateLock.unlock();}} else {// Contention failed, the thread will yield its time slice to wait for bucket available.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));}}}

获取某个时间的窗口方法。
先是计算出来这个时间戳对应窗口在窗口数组索引位置,以及窗口的起始时间。计算方法就是第一小节里面介绍的。
接着就是从这个窗口数组中获取这个位置的窗口,然后判断是不是等于null,如果等于null的话,说明之前没有过,就创建一个新的窗口,然后塞到数组中,返回就可以了,如果这个窗口不是null的话,先比较下这个窗口起始时间是否相等,如果相等的话,直接返回,如果是你要获取窗口时间 比 现在有的那个起始时间要小的话,说明你要的那个东西已经过期了,直接new一个给你了,如果是你要获取的时间比现在的那个起始时间要大的话,说明数组中那个比较老了,这个时候加锁,重置一下窗口,其实就是将里面的统计值重置成0 ,然后重置一下窗口起始时间就可以了,然后释放锁就可以了。
好了,到这我们sentinel的滑动窗口就解析完成了,其实这里面还有很多代码,这里我们就不一一看了。

Sentinel源码解析之滑动窗口相关推荐

  1. Java熔断框架有哪些_降级熔断框架 Hystrix 源码解析:滑动窗口统计

    降级熔断框架 Hystrix 源码解析:滑动窗口统计 概述 Hystrix 是一个开源的降级熔断框架,用于提高服务可靠性,适用于依赖大量外部服务的业务系统.什么是降级熔断呢? 降级 业务降级,是指牺牲 ...

  2. 阿里 Sentinel 源码解析

    点击上方蓝色"方志朋",选择"设为星标"回复"666"获取独家整理的学习资料! 本文介绍阿里开源的 Sentinel 源码,GitHub: ...

  3. Sentinel滑动时间窗限流算法原理及源码解析(上)

    文章目录 时间窗限流算法 滑动时间窗口 滑动时间窗口算法改进 滑动时间窗口源码解析 时间窗限流算法 10t到16t 10个请求 16t-20t 50个请求 20t-26t 60个请求 26t到30t ...

  4. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者"tingsking18"和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消 ...

  5. 深度思考 Spring Cloud + Alibaba Sentinel 源码原理

    随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度保护服务的稳定性. 作者 | 向寒 / 孙玄 来源 | 架构之美 ...

  6. kcp 介绍与源代码分析_KCP-GO源码解析

    原标题:KCP-GO源码解析 原文作者:张伯雨 golang技术社区 概念 ARQ:自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层的错误纠正协议之一. ...

  7. Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(一)

    Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(一) 1. LiDAR inertial odometry and mapping简介 ...

  8. RN FlatList使用详解及源码解析

    FlatList使用详解及源码解析 前言 长列表或者无限下拉列表是最常见的应用场景之一.RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧.而在最新的 0.43 版本中,提供 ...

  9. Redis源码解析——前言

    今天开启Redis源码的阅读之旅.对于一些没有接触过开源代码分析的同学来说,可能这是一件很麻烦的事.但是我总觉得做一件事,不管有多大多难,我们首先要在战略上蔑视它,但是要在战术上重视它.除了一些高大上 ...

最新文章

  1. java.lang.IncompatibleClassChangeError: Found interface org.apache.poi.util.POILogger, but class was
  2. 揭秘 | 连续3年支撑双11,阿里云神龙如何扛住全球流量洪峰?
  3. Sicily 1817 校歌手大奖赛
  4. 【LeetCode】200. 岛屿的个数
  5. 玩转Linux文件描述符和重定向
  6. 阿里巴巴开源技术汇总:115个软件(五)
  7. C和C++ const的声明差异
  8. 对于已有的【寄存】代码,【式样】变更,【参照】其他代码修正时的注意事项!
  9. Python中的几个重要函数
  10. lua游戏脚本自动打怪_了解Lua(官方文档翻译)
  11. SQL2005备份如何在SQL2000上还原
  12. 12 EDA技术实用教程【时序电路Verilog设计3】
  13. URI和URL、URN的作用和区别
  14. 如何快速在手机上修改证件照底色
  15. 深入理解C语言小括号用法
  16. 使用Hex view编写脚本生成特定格式刷写文件
  17. Filezilla Xshell SecureFX Win10等无法拖放文件(本地或线上)解决办法
  18. 【计算机毕业设计】092二手闲置交易市场系统
  19. 43.10. Google Authenticator - Android Apps on Google Play
  20. 帝国cms 评论 审核 php,帝国cms评论怎么做

热门文章

  1. 万集科技车路协同方案应用于国内首个智能网联高速公路测试基地
  2. 【社交电子商务时代下新媒体的运用思考】社交电子商务时代背景下新媒体应用的重要意义分析
  3. 完美解决 Git-Failed to connect to github.com port 443问题
  4. java函数式编程的原理的理解
  5. 计算机网络中www中文名称为,计算机等级考试试题及答案解析(网络知识) -备考资料...
  6. 华创期货:短线交易的艺术和策略
  7. MySQL学习【个人笔记/已完结】
  8. ajax 302 重定向不成功问题
  9. 单周涨粉20w+,佛系直播竟让他意外变身带货黑马?
  10. python多大的孩子_少儿python教材适合多大的孩子?孩子接触起来困难吗?