Sentinel源码四:StatisticNode
概述
上一篇解析了 NodeSelectorSlot 对资源的收集,其中资源会保存为 DefaultNode,而 DefaultNode 的核心能力都依赖于 StatisticNode 实现,所以这篇对 StatisticNode 做一个详细的解析。
解析
类图
StatisticNode

Node
Node 定义了对资源统计的接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50public interface Node extends OccupySupport, DebugSupport {
long totalRequest();
long totalPass();
long totalSuccess();
long blockRequest();
long totalException();
double passQps();
double blockQps();
double totalQps();
double successQps();
double maxSuccessQps();
double exceptionQps();
double avgRt();
double minRt();
int curThreadNum();
double previousBlockQps();
double previousPassQps();
Map<Long, MetricNode> metrics();
void addPassRequest(int count);
void addRtAndSuccess(long rt, int success);
void increaseBlockQps(int count);
void increaseExceptionQps(int count);
void increaseThreadNum();
void decreaseThreadNum();
void reset();
}
StatisticNode
StatisticNode 负责对资源进行统计比如通过的请求QPS、被限流的请求QPS、请求的平均RT等等
继承关系
1 | public class StatisticNode implements Node {} |
成员变量
1 | // 秒级时间窗口统计 |
核心方法
分析以下方法可以发现 StatisticNode 对资源的统计都是依赖 ArrayMetric 来实现的。
重置 reset
1 |
|
通过的请求数增加 addPassRequest
1 |
|
被限流的QPS增加 increaseBlockQps
1 |
|
计算平均rt
1 |
|
计算通过的QPS
1 |
|
尝试占用下一周期 tryOccupyNext
尝试借用后续时间窗格的通过数
1 |
|
指标类 Metric
Metric 提供了各种指标的操作方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public interface Metric extends DebugSupport {
long success();
long maxSuccess();
long exception();
long block();
long pass();
long rt();
long minRt();
List<MetricNode> details();
MetricBucket[] windows();
void addException(int n);
void addBlock(int n);
void addSuccess(int n);
void addPass(int n);
void addRT(long rt);
double getWindowIntervalInSec();
int getSampleCount();
long getWindowPass(long timeMillis);
void addOccupiedPass(int acquireCount);
void addWaiting(long futureTime, int acquireCount);
long waiting();
long occupiedPass();
long previousWindowBlock();
long previousWindowPass();
}
指标桶 MetricBucket
MetricBucket 负责保存指标信息,实际进行指标记录和计算的类,counters数组保存各种事件的计算值,minRt记录最小rt时间。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76public class MetricBucket {
private final LongAdder[] counters; // 数组每个元素代表一个时间的计数值
private volatile long minRt;
public MetricBucket() {
MetricEvent[] events = MetricEvent.values();
this.counters = new LongAdder[events.length];
for (MetricEvent event : events) {
counters[event.ordinal()] = new LongAdder();
}
initMinRt();
}
public MetricBucket reset(MetricBucket bucket) {
for (MetricEvent event : MetricEvent.values()) {
counters[event.ordinal()].reset();
counters[event.ordinal()].add(bucket.get(event));
}
initMinRt();
return this;
}
private void initMinRt() {
this.minRt = Constants.TIME_DROP_VALVE;
}
public MetricBucket reset() {
for (MetricEvent event : MetricEvent.values()) {
counters[event.ordinal()].reset();
}
initMinRt();
return this;
}
public long get(MetricEvent event) {
return counters[event.ordinal()].sum();
}
public MetricBucket add(MetricEvent event, long n) {
counters[event.ordinal()].add(n);
return this;
}
public long pass() {
return get(MetricEvent.PASS);
}
public long occupiedPass() {
return get(MetricEvent.OCCUPIED_PASS);
}
public long block() {
return get(MetricEvent.BLOCK);
}
public void addPass(int n) {
add(MetricEvent.PASS, n);
}
public void addOccupiedPass(int n) {
add(MetricEvent.OCCUPIED_PASS, n);
}
public void addRT(long rt) {
add(MetricEvent.RT, rt);
// Not thread-safe, but it's okay.
if (rt < minRt) {
minRt = rt;
}
}
// 省略部分方法
}
指标实现类 ArrayMetric
ArrayMetric 实现了指标接口 Metric,其中使用 LeapArray
继承关系
1 | public class ArrayMetric implements Metric {} |
成员变量
1 | private final LeapArray<MetricBucket> data; |
构造器
1 | public ArrayMetric(int sampleCount, int intervalInMs) { |
核心方法
已经block的请求数 block
1 |
|
增加Block的请求数 addBlock
1 |
|
其他的统计成功请求、异常请求数等方法与Block统计类似依赖于 LeapArray
LeapArray
Basic data structure for statistic metrics in Sentinel.
Leap array use sliding window algorithm to count data. Each bucket cover {@code windowLengthInMs} time span, and the total time span is {@link #intervalInMs}, so the total bucket amount is:
{@code sampleCount = intervalInMs / windowLengthInMs}.
成员变量
1 | protected int windowLengthInMs; // 一个窗口的实际长度(ms) |
WindowWrap 有三个属性:窗口时间长度windowLengthInMs、窗口开始时间windowStart、值value
AtomicReferenceArray与AtomicInteger之类的类似,是原子性的数组,更新元素时通过cas的方式更新。
构造器
1 | public LeapArray(int sampleCount, int intervalInMs) { |
核心方法
计算时间窗口序号 calculateTimeIdx
根据时间(timeMillis)按照时间窗口的长度计算落在哪个时间窗口
时间窗口序号 = 时间 / 时间窗口时间长度 % 窗口个数1
2
3
4
5private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
long timeId = timeMillis / windowLengthInMs;
// Calculate current index so we can map the timestamp to the leap array.
return (int)(timeId % array.length());
}
计算时间窗口开始时间 calculateWindowStart
根据时间窗口时间长度取整
开始时间 = 时间 - 时间%时间窗口时间长度
时间是15,时间窗口时间长度是10,则开始时间就是:
- 15 - 15%10 = 10
1 | protected long calculateWindowStart(/*@Valid*/ long timeMillis) { |
获取当前时间的时间窗口
1 | public WindowWrap<T> currentWindow() { |
获取当前时间的时间窗口的逻辑:
- 计算时间窗口的序号和时间窗口的开始时间;
- 获取时间窗口,有四种情况:
- 根据时间窗口序号获取的时间窗口对象为null,则new一个时间窗口对象cas更新;
- 计算的开始时间=时间窗口的开始时间,则返回这个时间窗口;
- 计算的开始时间>时间窗口的开始时间,则尝试获取更新锁并resetWindowTo返回;
- 计算的开始时间<时间窗口的开始时间,则返回一个空的。
1 | public WindowWrap<T> currentWindow(long timeMillis) { |
获取时间窗口中的值 getWindowValue
1 | public T getWindowValue(long timeMillis) { |
LeapArray的子类
| 类 | 功能 |
|---|---|
| FutureBucketLeapArray | 存储未来的Bucket |
| OccupiableBucketLeapArray | 内置FutureBucketLeapArray,处理等待的情况 |
FutureBucketLeapArray
1 | public class FutureBucketLeapArray extends LeapArray<MetricBucket> { |
OccupiableBucketLeapArray
OccupiableBucketLeapArray 类中持有 borrowArray(FutureBucketLeapArray)属性
com.alibaba.csp.sentinel.node.StatisticNode#addWaitingRequest -> com.alibaba.csp.sentinel.slots.statistic.metric.Metric#addWaiting
- 在添加Waiting计数的时候会使用borrowArray记录;
- 当newEmptyBucket的时候会将borrowArray中记录的数据返回给新的MetricBucket
1 | public class OccupiableBucketLeapArray extends LeapArray<MetricBucket> { |