资源调用树
- 调用链路树:Root为根结点的树,它的子节点是EntranceNode(Context),表示人为定义的调用上下文,EntranceNode的子节点是DefaultNode(Entry),表示资源调用,一个应用进程对应多个调用上下文,一个调用上下文对应多个资源
- Root: 根结点,一个应用进程只有一个Root
- Context: 调用上下文,一个应用可以创建多个,是人为定义的一组资源调用的集合,对应一个EntranceNode、上下文名称、来源。用于保存这一组资源调用的统计信息
- DefaultNode:对应同一上下文的同一个资源,同一资源在同一上下文中调用多次,对应同一个DefaultNode。用于保存一个资源的调用信息,DefaultNode还可以有多个子DefaultNode,表示该资源调用过程中又调用了其它资源,
- DefaultNode的统计信息不会求子DefaultNode的和,而是当前资源的统计信息,EntranceNode的统计信息是直接子节点的和
- Entry: 表示某个资源的具体一次调用,同一上下文同一资源的每一次调用都对应一个Entry,这些调用只会对应一个DefaultNode
- ProcessorChain:规则调用链,资源级的,同一资源对应同一个链,即使是在不同上下文中,但NodeSelectorSlot中又根据上下文名称区分了不同上下文的DefaultNode,所以DefaultNode是上下文+资源级的
- NodeSelectorSlot:规则调用链的第一个槽位,用于创建DefaultNode,并将它添加到调用树的当前调用的资源Node的子节点
- ClusterNode: 对应同一资源,不同上下文的资源也是对应同一个ClusterNode,默认情况下,即流控规则的来源配了default,并且时直接流控的话,使用ClusterNode来统计度量信息。
- ClusterBuilderSlot:创建ClusterNode,并将它保存到DefaultNode
- 流控规则的配置中,如果来源是default,流控规则是直接,则表示对当前资源进行流控;是关联,则表示对关联资源进行流控(获取关联资源的ClusterNode);是链路,表示对在指定上下文的当前资源进行流控
bash
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
//流控规则是关联,就是关联资源;是链路,就是入口资源
String refResource = rule.getRefResource();
int strategy = rule.getStrategy();
if (StringUtil.isEmpty(refResource)) {
return null;
}
//流控规则是关联
if (strategy == RuleConstant.STRATEGY_RELATE) {
return ClusterBuilderSlot.getClusterNode(refResource);
}
//是链路
if (strategy == RuleConstant.STRATEGY_CHAIN) {
if (!refResource.equals(context.getName())) {
return null;
}
return node;
}
// No node.
return null;
}
滑动时间窗
- 固定时间窗存在的问题:只统计当前请求所在的时间窗口的请求数,相邻的时间窗可能存在单位时间超出统计阈值
- 滑动时间窗:为了解决上面的问题,统计当前请求最近一段时间的统计值,但又引发了新的问题:需要存储最近一段时间每个请求的时间戳,每次请求重新计算最近一段时间的请求数,存在存储和效率问题,而且对于密集的请求,存在重复计算的问题
- 使用样本窗口的滑动时间窗:解决了滑动时间窗的问题,在滑动窗口内再细分多个样本窗口,新的请求数统计到当前样本窗口,已经过去的样本窗口不再重复计算,每次滑动n个样本窗口个单位的长度
统计数据的实现
bash
public abstract class LeapArray<T> {
//滑动窗口中样本的时间长度,windowLengthInMs=intervalInMs/sampleCount
protected int windowLengthInMs;
//滑动窗口中的样本数
protected int sampleCount;
//滑动窗口长度,毫秒
protected int intervalInMs;
//滑动窗口长度,秒
private double intervalInSecond;
//数组长度等于sampleCount,其中的元素是滑动窗口中每个样本的计量值
protected final AtomicReferenceArray<WindowWrap<T>> array;
}
//WindowWrap用于保存计量数据的公共部分
public class WindowWrap<T> {
//样本时间长度
private final long windowLengthInMs;
//样本开始时间戳
private long windowStart;
//具体的计量数据,就是MetricBucket
private T value;
}
public class MetricBucket {
//计量数据包含多个维度,维度类型在MetricEvent中,比如通过的请求数、异常数
private final LongAdder[] counters;
}
bash
//根据当前时间戳计算当前样本窗口的数组索引
private 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());
}
bash
//根据当前时间戳计算计算当前样本窗口的起始时间戳
protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
return timeMillis - timeMillis % windowLengthInMs;
}
根据索引获取样本窗口,如果为空则初始化,否则根据当前样本窗口的起始时间戳和已存在的样本窗口进行比较,如果相等,则说明为同一样本窗口,否则是已经过期的窗口,重置它的起始时间和计量值
获取统计数据的实现
需要过滤掉样本窗口数组中已过期的窗口,用当前时间戳-样本窗口起始时间,如果大于滑动窗口长度,则为过期的窗口
bash
public boolean isWindowDeprecated(long time, WindowWrap<T> windowWrap) {
return time - windowWrap.windowStart() > intervalInMs;
}