This commit is contained in:
by931
2022-09-06 22:30:37 +08:00
parent 66970f3e38
commit 3d6528675a
796 changed files with 3382 additions and 3382 deletions

View File

@@ -194,7 +194,7 @@ function hide_canvas() {
}
</code></pre>
<p>如上面代码所示Bucket 记录一段时间内的各项指标数据用的是一个 LongAdder 数组LongAdder 保证了数据修改的原子性,并且性能比 AtomicInteger 表现更好。数组的每个元素分别记录一个时间窗口内的请求总数、异常数、总耗时,如下图所示。</p>
<p><img src="assets/1c69e7b0-e0b0-11ea-b45a-99f77a1eea3e" alt="04-01-counters" /></p>
<p><img src="assets/1c69e7b0-e0b0-11ea-b45a-99f77a1eea3e" alt="png" /></p>
<p>Sentinel 用枚举类型 MetricEvent 的 ordinal 属性作为下标ordinal 的值从 0 开始,按枚举元素的顺序递增,正好可以用作数组的下标。在 qps-helper 中LongAdder 被替换为 j.u.c 包下的 atomic 类了,并且只保留 EXCEPTION、SUCCESS、RT代码如下。</p>
<pre><code class="language-java">// 事件类型
public enum MetricEvent {
@@ -220,11 +220,11 @@ public void add(MetricEvent event, long n) {
<h3>滑动窗口</h3>
<p>如果我们希望能够知道某个接口的每秒处理成功请求数(成功 QPS、每秒处理失败请求数失败 QPS以及处理每个成功请求的平均耗时avg RT我们只需要控制 Bucket 统计一秒钟的指标数据即可。我们只需要控制 Bucket 统计一秒钟内的指标数据即可。但如何才能确保 Bucket 存储的就是精确到 1 秒内的数据呢?</p>
<p>最 low 的做法就是启一个定时任务每秒创建一个 Bucket但统计出来的数据误差绝对很大。Sentinel 是这样实现的,它定义一个 Bucket 数组,根据时间戳来定位到数组的下标。假设我们需要统计每 1 秒处理的请求数等数据,且只需要保存最近一分钟的数据。那么 Bucket 数组的大小就可以设置为 60每个 Bucket 的 windowLengthInMs窗口时间大小就是 1000 毫秒1 秒),如下图所示。</p>
<p><img src="assets/3968d920-e0b0-11ea-8f3f-a12c78e9865c" alt="04-02-bucket" /></p>
<p><img src="assets/3968d920-e0b0-11ea-8f3f-a12c78e9865c" alt="png" /></p>
<p>由于每个 Bucket 存储的是 1 秒的数据,假设 Bucket 数组的大小是无限大的,那么我们只需要将当前时间戳去掉毫秒部分就能得到当前的秒数,将得到的秒数作为索引就能从 Bucket 数组中获取当前时间窗口的 Bucket。</p>
<p>一切资源均有限,所以我们不可能无限的存储 Bucket我们也不需要存储那么多历史数据在内存中。当我们只需要保留一分钟的数据时Bucket 数组的大小就可以设置为 60我们希望这个数组可以循环使用并且永远只保存最近 1 分钟的数据,这样不仅可以避免频繁的创建 Bucket也减少内存资源的占用。</p>
<p>这种情况下如何定位 Bucket 呢?我们只需要将当前时间戳去掉毫秒部分得到当前的秒数,再将得到的秒数与数组长度取余数,就能得到当前时间窗口的 Bucket 在数组中的位置(索引),如下图所示:</p>
<p><img src="assets/4223a680-e0b0-11ea-9345-e7c4c4dd55f7" alt="04-03-window" /></p>
<p><img src="assets/4223a680-e0b0-11ea-9345-e7c4c4dd55f7" alt="png" /></p>
<p>根据当前时间戳计算出当前时间窗口的 Bucket 在数组中的索引,算法实现如下:</p>
<pre><code class="language-java"> private int calculateTimeIdx(long timeMillis) {
/**
@@ -355,7 +355,7 @@ public void add(MetricEvent event, long n) {
<h3>获取当前时间戳的前一个 Bucket</h3>
<p>根据当前时间戳计算出当前 Bucket 的时间窗口开始时间,用当前 Bucket 的时间窗口开始时间减去一个窗口时间大小就能定位出前一个 Bucket。</p>
<p>由于是使用数组实现滑动窗口,数组的每个元素都会被循环使用,因此当前 Bucket 与前一个 Bucket 会有相差一个完整的滑动窗口周期的可能,如下图所示。</p>
<p><img src="assets/638d4510-e0b0-11ea-b1dc-4f04d04fa39d" alt="04-04-pre-cut-ptr" /></p>
<p><img src="assets/638d4510-e0b0-11ea-b1dc-4f04d04fa39d" alt="png" /></p>
<p>当前时间戳对应的 Bucket 的时间窗口开始时间戳为 1595974702000而前一个 Bucket 的时间窗口开始时间戳可能是 1595974701000也可能是一个滑动窗口周期之前的 1595974641000。所以在获取到当前 Bucket 的前一个 Bucket 时,需要根据 Bucket 的时间窗口开始时间与当前时间戳比较,如果跨了一个周期就是无效的。</p>
<h3>总结</h3>
<ul>