高级篇-sentinel限流slot责任链设计(一)
前言
在这之前,发的文章都是一些简单的,入门级别的介绍,涉及源码级别的介绍并不多,本文开始,就会逐渐的开始介入源码的讲解。
本文会从sentinel的核心知识点 ,slot机制来讲解。
源码入口
下面的代码是调用SphU.entry("HelloWorld") 之后进去的核心方法,整个的限流操作都是在这里面完成。
源码入口: com.alibaba.csp.sentinel.CtSph#entryWithPriority
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
//省略大部分代码。。。。
// 构建slot链,这个地方就是构建责任链的地方
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); // 代码1
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
// 开始执行责任链
chain.entry(context, resourceWrapper, null, count, prioritized, args); // 代码2
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
// This should not happen, unless there are errors existing in Sentinel internal.
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}
上面的代码主要分为两步,
代码1: 构建责任链
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
// 根据资源获取slot功能链
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
// 上锁保证仅会初始化一个
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit. 最大的资源大小是6000个
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
//构建一个slot引用链 -----
chain = SlotChainProvider.newSlotChain();
// map存储
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
上面的代码简单来说,就是从chainMap
里面获取slot功能链, 没有的话,就构建一个,这里需要注意一点Constants.MAX_SLOT_CHAIN_SIZE
, chainMap是限制了大小,最大不能超过6000, 也就是说,默认不能超过6000个资源,如果超过6000个资源,则会有资源的限流没办法生效
SlotChainProvider.newSlotChain()
通过这个方法来进行构建的。
public static ProcessorSlotChain newSlotChain() { // 不为空,则直接返回 if (slotChainBuilder != null) { return slotChainBuilder.build(); }
// 构建slot chain ,通过SPI机制来 slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class); // SPI获取不到的话,则使用默认的 if (slotChainBuilder == null) { // Should not go through here. RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default"); slotChainBuilder = new DefaultSlotChainBuilder(); } else { RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: " + slotChainBuilder.getClass().getCanonicalName()); } // 返回结果 return slotChainBuilder.build(); }
通过SPI机制来构建slot chain, com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
具体加载的上面SlotChainBuilder
可以查看所有项目(META-IN/services)下的这个文件.
代码2:执行责任链
构建出来的slot链如下,执行顺序也是这样。
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级
ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
此插槽用于构建资源的 ClusterNode
以及调用来源节点。ClusterNode
保持某个资源运行统计信息(响应时间、QPS、block 数目、线程数、异常数等)以及调用来源统计信息列表。调用来源的名称由 ContextUtil.enter(contextName,origin)
中的 origin
标记。可通过如下命令查看某个资源不同调用者的访问情况:curl http://localhost:8719/origin?id=caller
:
id: nodeA
idx origin threadNum passedQps blockedQps totalQps aRt 1m-passed 1m-blocked 1m-total
1 caller1 0 0 0 0 0 0 0 0
2 caller2 0 0 0 0 0 0 0 0
LogSlot
用于输出拦截日志“sentinel-block.log”
StatisticSlot:
StatisticSlot
是 Sentinel 的核心功能插槽之一,用于统计实时的调用数据。
clusterNode
:资源唯一标识的 ClusterNode 的实时统计origin
:根据来自不同调用者的统计信息defaultnode
: 根据入口上下文区分的资源 ID 的 runtime 统计- 入口流量的统计
Sentinel 底层采用高性能的滑动窗口数据结构 LeapArray
来统计实时的秒级指标数据,可以很好地支撑写多于读的高并发场景。
FlowSlot
这个 slot 主要根据预设的资源的统计信息,按照固定的次序,依次生效。如果一个资源对应两条或者多条流控规则,则会根据如下次序依次检验,直到全部通过或者有一个规则生效为止:
指定应用生效的规则,即针对调用方限流的;
调用方为 other 的规则;
调用方为 default 的规则。
AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
DegradeSlot:
这个 slot 主要针对资源的平均响应时间(RT)以及异常比率,来决定资源是否在接下来的时间被自动熔断掉。
SystemSlot
这个 slot 会根据对于当前系统的整体情况,对入口资源的调用进行动态调配。其原理是让入口的流量和当前系统的预计容量达到一个动态平衡。
注意系统规则只对入口流量起作用(调用类型为 EntryType.IN
),对出口流量无效。可通过 SphU.entry(res, entryType)
指定调用类型,如果不指定,默认是EntryType.OUT
。
总体的框架如下:
Sentinel 将 ProcessorSlot
作为 SPI 接口进行扩展(1.7.2 版本以前 SlotChainBuilder
作为 SPI),使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。
slot的设计是sentinel的核心设计,采用责任链的模式,通过一个一个的功能卡槽去进行去完成对应的,这种设计有好处也有坏处。
优点:就是可以非常简单的加入新的功能,扩展性非常强
缺点:每个功能槽的功能很独立,统计数据是一个,判断规则是一个, 这样会导致统计数据和使用数据分离,线程安全是个问题,会导致实际的限流会有一定程度的不准确
sharedCode源码交流群,欢迎喜欢阅读源码的朋友加群,添加下面的微信, 备注”加群“ 。
前言
在这之前,发的文章都是一些简单的,入门级别的介绍,涉及源码级别的介绍并不多,本文开始,就会逐渐的开始介入源码的讲解。
本文会从sentinel的核心知识点 ,slot机制来讲解。
源码入口
下面的代码是调用SphU.entry(“HelloWorld”) 之后进去的核心方法,整个的限流操作都是在这里面完成。
源码入口: com.alibaba.csp.sentinel.CtSph#entryWithPriority
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
//省略大部分代码。。。。
// 构建slot链,这个地方就是构建责任链的地方
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); // 代码1
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
// 开始执行责任链
chain.entry(context, resourceWrapper, null, count, prioritized, args); // 代码2
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
// This should not happen, unless there are errors existing in Sentinel internal.
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}
上面的代码主要分为两步,
代码1: 构建责任链
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
// 根据资源获取slot功能链
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
// 上锁保证仅会初始化一个
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit. 最大的资源大小是6000个
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
//构建一个slot引用链 -----
chain = SlotChainProvider.newSlotChain();
// map存储
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
上面的代码简单来说,就是从chainMap
里面获取slot功能链, 没有的话,就构建一个,这里需要注意一点Constants.MAX_SLOT_CHAIN_SIZE
, chainMap是限制了大小,最大不能超过6000, 也就是说,默认不能超过6000个资源,如果超过6000个资源,则会有资源的限流没办法生效
SlotChainProvider.newSlotChain()
通过这个方法来进行构建的。
public static ProcessorSlotChain newSlotChain() {
// 不为空,则直接返回
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
// 构建slot chain ,通过SPI机制来
slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);
// SPI获取不到的话,则使用默认的
if (slotChainBuilder == null) {
// Should not go through here.
RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
slotChainBuilder = new DefaultSlotChainBuilder();
} else {
RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: "
+ slotChainBuilder.getClass().getCanonicalName());
}
// 返回结果
return slotChainBuilder.build();
}
通过SPI机制来构建slot chain, com.alibaba.csp.sentinel.slotchain.SlotChainBuilder
具体加载的上面SlotChainBuilder
可以查看所有项目(META-IN/services)下的这个文件.
代码2:执行责任链
构建出来的slot链如下,执行顺序也是这样。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KhWMEvGs-1653975735080)(https://se-dd.com//shared/file/oneblog/20210408180701960.png)]
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级
ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
此插槽用于构建资源的 ClusterNode
以及调用来源节点。ClusterNode
保持某个资源运行统计信息(响应时间、QPS、block 数目、线程数、异常数等)以及调用来源统计信息列表。调用来源的名称由 ContextUtil.enter(contextName,origin)
中的 origin
标记。可通过如下命令查看某个资源不同调用者的访问情况:curl http://localhost:8719/origin?id=caller
:
id: nodeA
idx origin threadNum passedQps blockedQps totalQps aRt 1m-passed 1m-blocked 1m-total
1 caller1 0 0 0 0 0 0 0 0
2 caller2 0 0 0 0 0 0 0 0
LogSlot
用于输出拦截日志“sentinel-block.log”
StatisticSlot:
StatisticSlot
是 Sentinel 的核心功能插槽之一,用于统计实时的调用数据。
clusterNode
:资源唯一标识的 ClusterNode 的实时统计origin
:根据来自不同调用者的统计信息defaultnode
: 根据入口上下文区分的资源 ID 的 runtime 统计- 入口流量的统计
Sentinel 底层采用高性能的滑动窗口数据结构 LeapArray
来统计实时的秒级指标数据,可以很好地支撑写多于读的高并发场景。
FlowSlot
这个 slot 主要根据预设的资源的统计信息,按照固定的次序,依次生效。如果一个资源对应两条或者多条流控规则,则会根据如下次序依次检验,直到全部通过或者有一个规则生效为止:
-
指定应用生效的规则,即针对调用方限流的;
-
调用方为 other 的规则;
-
调用方为 default 的规则。
AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
DegradeSlot:
这个 slot 主要针对资源的平均响应时间(RT)以及异常比率,来决定资源是否在接下来的时间被自动熔断掉。
SystemSlot
这个 slot 会根据对于当前系统的整体情况,对入口资源的调用进行动态调配。其原理是让入口的流量和当前系统的预计容量达到一个动态平衡。
注意系统规则只对入口流量起作用(调用类型为 EntryType.IN
),对出口流量无效。可通过 SphU.entry(res, entryType)
指定调用类型,如果不指定,默认是EntryType.OUT
。
总体的框架如下:
Sentinel 将 ProcessorSlot
作为 SPI 接口进行扩展(1.7.2 版本以前 SlotChainBuilder
作为 SPI),使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。
slot的设计是sentinel的核心设计,采用责任链的模式,通过一个一个的功能卡槽去进行去完成对应的,这种设计有好处也有坏处。
优点:就是可以非常简单的加入新的功能,扩展性非常强
缺点:每个功能槽的功能很独立,统计数据是一个,判断规则是一个, 这样会导致统计数据和使用数据分离,线程安全是个问题,会导致实际的限流会有一定程度的不准确