前言:
如今看官们对“滑动窗口算法c语言”大致比较注重,你们都想要了解一些“滑动窗口算法c语言”的相关内容。那么小编在网上收集了一些关于“滑动窗口算法c语言””的相关资讯,希望小伙伴们能喜欢,大家一起来了解一下吧!基于滑动窗口实现资源指标数据统计
Sentinel是基于滑动窗口实现资源的实时指标数据统计的。如果要深入理解Sentinel的限流实现原理,就要先了解如何实现其指标数据的统计,例如,如何统计QPS。
为了学起来比较简单,我们不直接分析Sentinel的源码,而是分析笔者从Sentinel中摘抄的且经过改造后的qps-helper工具包的代码。这两者总体上是一样的,只是笔者去掉了一些指标数据统计,将Sentinel一些自定义的类替换成了JDK提供的类,并封装成了通用的QPS统计工具包。
当然,也可以直接查看Sentinel的源码,其源码在sentinel-core模块的slots包下。
Bucket
Sentinel使用Bucket统计一段时间内的各项指标数据,这些指标数据包括请求总数、成功总数、异常总数、总耗时、最小耗时等。一个Bucket可以记录1秒内的数据,也可以记录10毫秒内的数据,这由采样周期决定。采样周期就是每个Bucket的时间窗口大小。
在qps-helper中,Bucket的实现类为MetricBucket,只统计请求成功总数、请求异常总数、总耗时和最小耗时,代码如下。
• counters:存储各项指标的计数,包括请求异常总数、请求成功总数和总耗时等。
• minRt:只记录最小耗时。
MetricBucket使用LongAdder数组记录一段时间内的各项指标数据,LongAdder数组的每个元素分别记录请求成功总数、请求异常总数和总耗时,如图4.1所示。
提示:LongAdder数组保证了数据修改的原子性,并且性能比AtomicInteger更好。
Sentinel使用枚举类型MetricEvent的ordinal属性作为数组的下标,因为ordinal的值从0开始,按枚举元素的顺序递增,正好可以用作数组的下标。
在qps-helper中,MetricBucket的LongAdder数组已经替换成j.u.c包下的LongAdder类,并且MetricEvent只保留了EXCEPTION、SUCCESS和RT,代码如下。
• EXCEPTION:请求异常总数指标,对应的数组下标为0。
• SUCCESS:请求成功总数指标,对应的数组下标为1。
• RT:总耗时指标,对应的数组下标为2。
当需要获取time-window(时间窗口)内所有Bucket统计请求成功总数、请求异常总数或总耗时时,可以根据MetricEvent从每个Bucket的LongAdder数组中获取对应的LongAdder实例,并调用sum方法计算总数,代码如下。
当需要Bucket记录一个成功请求、一个异常请求或一个处理请求的耗时时,可以根据MetricEvent从Bucket的LongAdder数组中获取对应的LongAdder实例,并调用其add方法,代码如下。
当需要Bucket记录一个成功请求、一个异常请求或一个处理请求的耗时时,可以根据MetricEvent从Bucket的LongAdder数组中获取对应的LongAdder实例,并调用其add方法,代码如下。
滑动窗口
如果想知道某个接口的每秒请求成功总数(successQPS)、每秒请求失败总数(exception QPS),以及处理每个请求的平均耗时(avg RT),则只需要设置滑动窗口的大小为1秒即可。但如果想获取最近1分钟内的某1秒的QPS指标数据,就需要将滑动窗口的大小设置为1分钟,采样总数设置为60,采样周期设置为1秒,这时,该滑动窗口拥有60个Bucket,每个Bucket可以统计1秒的指标数据,若想要获取某1秒的Bucket,则只需要根据时间戳定位Bucket即可。
假设滑动窗口的大小为1分钟,采样总数为60,采样周期为1秒,给定一个时间戳如何定位到Bucket呢?
首先将当前时间戳去掉毫秒部分得到当前的秒数,然后将得到的秒数与数组长度做取余运算,即可得到当前时间窗口的Bucket在数组中的位置,如图4.2所示。
定位当前时间戳对应的Bucket在滑动窗口的Bucket数组中的位置,其算法实现如下。
• windowLengthInMs:Bucket的时间窗口大小。
• timeMillis:当前时间戳。
• array:当前滑动窗口的Bucket数组。
想要获取连续的1分钟内的Bucket数据,就不能简单地从头开始遍历数组,正确做法是指定一个开始时间戳和结束时间戳,从开始时间戳开始计算Bucket存放在数组中的下标,根据下标获取Bucket,然后在每次循环时将开始时间戳加上1秒,直到开始时间戳等于结束时间戳。
由于滑动窗口会随着时间向前滑动,Bucket数组会被循环使用,当前时间戳与1分钟之前的时间戳和1分钟之后的时间戳都会被映射到数组中的同一个Bucket,因此必须能够判断指定的时间戳是否在取得的Bucket的时间窗口内,这就要求数组中的每个元素都要存储Bucket的时间窗口的开始时间戳和时间窗口大小。
例如,当前时间戳是1577017699235,Bucket的时间窗口大小是1秒,将时间戳的毫秒部分全部替换为0,就能得到Bucket的时间窗口的开始时间戳为1577017699000。
计算Bucket的时间窗口的开始时间戳的代码如下。
• windowLengthInMs:Bucket的时间窗口大小。
• timeMillis:当前时间戳。
WindowWrap
因为Bucket自身并不保存时间窗口信息,所以Sentinel给Bucket添加了一个包装类WindowWrap,用于记录Bucket的时间窗口信息。WindowWrap类的源码如下。
• windowLengthInMs:Bucket的时间窗口大小。• windowStart:时间窗口的开始时间戳。
• value:被包装的Bucket。
假设Bucket的时间窗口大小为1秒,那么Bucket统计的就是1秒内的请求成功总数、请求异常总数、总耗时等指标数据。如果时间窗口为[1577017699000,1577017699999],那么
1577017699000就是该Bucket的时间窗口的开始时间戳(windowStart),因为1秒等于1000毫秒,所以1000毫秒就是该Bucket的时间窗口大小(windowLengthInMs)。
windowStart+windowLengthInMs=时间窗口结束时间给定一个时间戳,判断该时间戳是否在Bucket的时间窗口内的算法实现如下。
通过时间戳定位Bucket
Bucket用于统计各项指标数据,WindowWrap类用于记录Bucket的时间窗口信息(包括时间窗口的开始时间戳和大小),而WindowWrap数组就是一个滑动窗口。
当收到一个请求时,可以根据收到请求时的时间戳和滑动窗口大小计算出一个索引值,从滑动窗口(WindowWrap数组)中获取一个WindowWrap类,从而获取WindowWrap类包装的Bucket,并调用Bucket实例的add方法统计指标。
根据当前时间戳定位Bucket的算法实现如下。
上述代码实现的功能:通过当前时间戳计算出当前时间窗口的Bucket(New Bucket)在数组中的索引(cidx),以及Bucket的时间窗口的开始时间戳,并通过索引从数组中获取Bucket(Old Bucket)。
① 当cidx处不存在Bucket时,创建一个新的Bucket,并且确保线程被安全写入数组中的cidx处,将此Bucket返回。
② 当Old Bucket不为空,且Old Bucket的时间窗口的开始时间戳与当前计算得到的NewBucket的时间窗口的开始时间戳相等时,该Bucket就是当前要找的Bucket,将此Bucket直接返回。
③ 当计算出New Bucket的时间窗口的开始时间戳大于当前数组中的cidx处存储的OldBucket的时间窗口的开始时间戳时,可以复用这个OldBucket,确保线程能够安全地重置Bucket,并返回Bucket。
④ 当计算出New Bucket的时间窗口的开始时间戳小于当前数组中的cidx处存储的OldBucket的时间窗口的开始时间戳时,直接返回一个空的Bucket,因为时间不会倒退。
获取当前时间戳的前一个Bucket
根据当前时间戳计算出当前Bucket的时间窗口的开始时间戳,使用当前Bucket的时间窗口的开始时间戳减去Bucket的时间窗口大小就能定位出前一个Bucket。
由于滑动窗口在实现上使用的数据结构是数组,数组的每个元素都会被循环使用,因此当前Bucket与前一个Bucket可能会相差一个完整的滑动窗口周期,如图4.3所示。
如果当前时间戳对应的Bucket的时间窗口的开始时间戳为1595974702000,那么前一个Bucket的时间窗口的开始时间戳可能是1595974701000,也可能是一个滑动窗口周期之前的时间戳1595974641000。
因此,在获取当前Bucket的前一个Bucket时,需要将Bucket的时间窗口的开始时间戳与当前时间戳比较,如果跨越了一个滑动窗口周期,就说明这个Bucket不是我们想要的。
本文给大家讲解的内容是深度解析微服务高并发资源指标数据统计:基于滑动窗口实现资源指标数据统计下篇文章给大家讲解的内容是深度解析微服务高并发资源指标数据统计:资源指标数据统计全解析感谢大家的支持!
标签: #滑动窗口算法c语言