从本篇文章开始,就要介绍 Sentinel 限流降级功能的核心了,前面也说过 Sentinel 使用了一套类似于责任链的模式来实现这个部分,这里我们展开一下,将责任链中的各个部分分别详细的介绍一下。更多相关文章和其他文章均收录于贝贝猫的文章目录。
上图仅作为设计思想的展示,图中 Slot 的顺序已和最新版 Sentinel Slot Chain 顺序不一致
前面我们已解说了,Sentinel 中最核心的功能都是通过一套处理链(责任链)来实现,处理链中的每一个处理单元被称为一个 Slot。每个 Slot 执行完业务逻辑处理后,都会触发下一个节点的处理方法,如此往复直到最后一个Slot,由此就形成了sentinel的责任链。这里我们先简单地回顾一下各个 Slot 的职责:
那么整个处理链是从哪开始运作起来的呢?其实 SphU#entry
就是整个处理链的入口。这里我们以最完整的两个 entry
接口为例,介绍 Sentinel 处理链的准备工作。
// SphU#entry 的下层接口 com.alibaba.csp.sentinel.CtSph#entry
@Override
public Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException {
MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
return entry(resource, count, args);
}
@Override
public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
StringResourceWrapper resource = new StringResourceWrapper(name, type);
return entry(resource, count, args);
}
在 CtSph#entry
这一层主要是做 Resource 的创建,最基础的就是我们前面所使用的 String 类型标识的 Resource,其中主要保存了 Resource 的 name 和该流量是流入性(EntryType#IN)还是流出行(EntryType#OUT),后续的限流降级规则中会有根据数据流入类型而做区分处理的情况。
// StringResourceWrapper 的父类 ResourceWrapper 的核心属性
protected final String name;
protected final EntryType entryType;
而 Method 类型的 Resource,在一些自适应的框架中用到的比较多,比如基于注解标识资源等。MethodResourceWrapper 和 StringResourceWrapper 一样继承自 ResourceWrapper,它会用函数的签名作为资源名。
// com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper#MethodResourceWrapper
public MethodResourceWrapper(Method method, EntryType e, int resType) {
super(MethodUtil.resolveMethodName(method), e, resType);
this.method = method;
}
// com.alibaba.csp.sentinel.util.MethodUtil#resolveMethodName
public static String resolveMethodName(Method method) {
if (method == null) {
throw new IllegalArgumentException("Null method");
}
String methodName = methodNameMap.get(method);
if (methodName == null) {
synchronized (LOCK) {
methodName = methodNameMap.get(method);
if (methodName == null) {
StringBuilder sb = new StringBuilder();
String className = method.getDeclaringClass().getName();
String name = method.getName();
Class<?>[] params = method.getParameterTypes();
sb.append(className).append(":").append(name);
sb.append("(");
int paramPos = 0;
for (Class<?> clazz : params) {
sb.append(clazz.getCanonicalName());
if (++paramPos < params.length) {
sb.append(",");
}
}
sb.append(")");
methodName = sb.toString();
methodNameMap.put(method, methodName);
}
}
}
return methodName;
}
对 Resource 类型进行区分之后,就到了处理链的组织和执行阶段了,这部分代码位于 CtSph#entryWithPriority
中:
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
// The {@link NullContext} indicates that the amount of context has exceeded the threshold,
// so here init the entry only. No rule checking will be done.
return new CtEntry(resourceWrapper, /*chain*/null, context);
}
if (context == null) {
// Using default context.
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
// Global switch is close, no rule checking will do.
if (!Constants.ON) {
return new CtEntry(resourceWrapper, /*chain*/null, context);
}
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
/*
* Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
* so no rule checking will be done.
*/
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);
} 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;
}
处理链的构建同样是以 Double-Check 的方式进行的,Sentinel 中会用 Resource 的 name 作为 key 将已经构建好的处理链保存在 Map 中,方便后续使用。这里,也会限制处理链的最大数量为 6000。
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit. MAX_SLOT_CHAIN_SIZE = 6000
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
chain = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
// Resource 对象的 equals 和 hashcode 函数
@Override
public int hashCode() {
return getName().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ResourceWrapper) {
ResourceWrapper rw = (ResourceWrapper)obj;
return rw.getName().equals(getName());
}
return false;
}
至于处理链的构建,Sentinel 提供了一个 SlotChainBuilder SPI,用户可以实现自己的 SlotChainBuilder,当然也能使用默认的实现。
public static ProcessorSlotChain newSlotChain() {
if (slotChainBuilder != null) {
return slotChainBuilder.build();
}
// Resolve the slot chain builder SPI.
slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);
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();
}
在默认的 SlotChainBuilder 实现 DefaultSlotChainBuilder 中会用到 Sentinel 所暴露的另一个 SPI——ProcessorSlot,DefaultSlotChainBuilder 中会将所有继承自 AbstractLinkedProcessorSlot(链式调用过程在这里实现)
的 ProcessorSlot 实现类实例化,并保存在处理链 ProcessorSlotChain 中,之所以每个 Slot 都要实例化一个新的对象是因为很多 Slot 都是有状态的。
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// Note: the instances of ProcessorSlot should be different, since they are not stateless.
List<ProcessorSlot> sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class);
for (ProcessorSlot slot : sortedSlotList) {
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
continue;
}
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
}
Sentinel 在加载各个 ProcessorSlot 的过程中还会根据各个 Slot 实现类通过 @SpiOrder
指定的顺序进行排序,前面所说的 ProcessSlotChain 的最终顺序,就是通过这种方式确立的,只不过目前代码中所定义的顺序已经和上图中的顺序有一定的出入,Sentinel 会按照 SpiOrder
升序排列各个 Slot,如下展示的就是现阶段(v1.7.2)各个 Slot 的顺序。
所谓处理链实际上就是一个 ProcessorSlot 实例的数组,在进入调用点时,执行 ProcessorSlot 的 entry 函数,每个 Slot 的 entry 任务都各不相同,但是每一个 Slot 在处理完自己的活之后都要通过 fireEntry 调用处理链中下一个 Slot 的 entry 函数。同理 exit 是在退出调用点的时候执行。
public interface ProcessorSlot<T> {
void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,
Object... args) throws Throwable;
void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,
Object... args) throws Throwable;
void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
}
NodeSelectorSlot 是排在最前面的 Slot,其中负责维护当前 Resource 对于不同入口(EntranceNode)的统计节点(DefaultNode)。同时它也负责维护各个节点之间的链路关系。将 DefaultNode 保存到 Context 中之后,就会调用下一 Slot。
// com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
DefaultNode node = map.get(context.getName());
if (node == null) {
synchronized (this) {
node = map.get(context.getName());
if (node == null) {
node = new DefaultNode(resourceWrapper, null);
HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
cacheMap.putAll(map);
cacheMap.put(context.getName(), node);
map = cacheMap;
// Build invocation tree
((DefaultNode) context.getLastNode()).addChild(node);
}
}
}
// 指定当前 Entry 所对应的 DefaultNode
context.setCurNode(node);
// 注意这里将 DefaultNode 作为参数传递给了下一 Slot,因为后续限流 Slot 会用这个 DefaultNode 来做链路模式的限流
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
if (next != null) {
// 调用下一个 Slot
next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
}
}
@SuppressWarnings("unchecked")
void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
throws Throwable {
T t = (T)o;
// 做泛型转换
entry(context, resourceWrapper, t, count, prioritized, args);
}
执行完 NodeSelectSlot 之后,调用树就会变成如下形态。
在 NodeSelectorSlot 中各个资源针对不同 Context 的统计节点会以如下方式保存。
ClusterBuilderSlot 负责对某一资源在不同类型 Context 中的流量进行聚合,创建各个 Resource 的汇总统计节点(ClusterNode),同时对于指定了来源(Origin)的流量,会创建当前资源专属于各个 Origin 的流量统计节点。最后将上述两类统计节点保存在 Context 中, 并调用下一个 Slot。
// com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args)
throws Throwable {
if (clusterNode == null) {
synchronized (lock) {
if (clusterNode == null) {
// Create the cluster node.
clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
newMap.putAll(clusterNodeMap);
newMap.put(node.getId(), clusterNode);
clusterNodeMap = newMap;
}
}
}
node.setClusterNode(clusterNode);
/*
* if context origin is set, we should get or create a new {@link Node} of
* the specific origin.
*/
if (!"".equals(context.getOrigin())) {
Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
context.getCurEntry().setOriginNode(originNode);
}
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
// com.alibaba.csp.sentinel.node.ClusterNode#getOrCreateOriginNode
public Node getOrCreateOriginNode(String origin) {
// originCountMap 是 ClusterNode 实例的成员变量
StatisticNode statisticNode = originCountMap.get(origin);
if (statisticNode == null) {
lock.lock();
try {
statisticNode = originCountMap.get(origin);
if (statisticNode == null) {
// The node is absent, create a new node for the origin.
statisticNode = new StatisticNode();
HashMap<String, StatisticNode> newMap = new HashMap<>(originCountMap.size() + 1);
newMap.putAll(originCountMap);
newMap.put(origin, statisticNode);
originCountMap = newMap;
}
} finally {
lock.unlock();
}
}
return statisticNode;
}
执行完 ClusterBuilderSlot 之后,调用树达到了最终形态。
在 ClusterBuilderSlot 中各个资源以及各个资源来自不同 Origin 的流量统计节点会以如下方式保存。
LogSlot 的工作很简单,内部直接调用下一个 Slot 的处理过程,如果发生了限流降级,就通过 EagleEyeLogUtil 进行记录(内部有线程负责周期性地将统计数据写入log),如果期间发生了异常,就记录到 Log 中。
// com.alibaba.csp.sentinel.slots.logger.LogSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args)
throws Throwable {
try {
fireEntry(context, resourceWrapper, obj, count, prioritized, args);
} catch (BlockException e) {
EagleEyeLogUtil.log(resourceWrapper.getName(), e.getClass().getSimpleName(), e.getRuleLimitApp(),
context.getOrigin(), count);
throw e;
} catch (Throwable e) {
RecordLog.warn("Unexpected entry exception", e);
}
}
StatisticSlot 是比较复杂的一个 Slot,各个维度的流量统计都是通过这个 Slot 处理的,这里 StatisticSlot 会先调用后续 Slot 的处理过程,然后根据后续 Slot 的处理结果开展不同的统计工作:
// com.alibaba.csp.sentinel.slots.statistic.StatisticSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
try {
// Do some checking.
fireEntry(context, resourceWrapper, node, count, prioritized, args);
// Request passed, add thread count and pass count.
node.increaseThreadNum();
node.addPassRequest(count);
if (context.getCurEntry().getOriginNode() != null) {
// Add count for origin node.
context.getCurEntry().getOriginNode().increaseThreadNum();
context.getCurEntry().getOriginNode().addPassRequest(count);
}
if (resourceWrapper.getEntryType() == EntryType.IN) {
// Add count for global inbound entry node for global statistics.
Constants.ENTRY_NODE.increaseThreadNum();
Constants.ENTRY_NODE.addPassRequest(count);
}
// Handle pass event with registered entry callback handlers.
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
handler.onPass(context, resourceWrapper, node, count, args);
}
} catch (PriorityWaitException ex) {
node.increaseThreadNum();
if (context.getCurEntry().getOriginNode() != null) {
// Add count for origin node.
context.getCurEntry().getOriginNode().increaseThreadNum();
}
if (resourceWrapper.getEntryType() == EntryType.IN) {
// Add count for global inbound entry node for global statistics.
Constants.ENTRY_NODE.increaseThreadNum();
}
// Handle pass event with registered entry callback handlers.
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
handler.onPass(context, resourceWrapper, node, count, args);
}
} catch (BlockException e) {
// Blocked, set block exception to current entry.
context.getCurEntry().setBlockError(e);
// Add block count.
node.increaseBlockQps(count);
if (context.getCurEntry().getOriginNode() != null) {
context.getCurEntry().getOriginNode().increaseBlockQps(count);
}
if (resourceWrapper.getEntryType() == EntryType.IN) {
// Add count for global inbound entry node for global statistics.
Constants.ENTRY_NODE.increaseBlockQps(count);
}
// Handle block event with registered entry callback handlers.
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
handler.onBlocked(e, context, resourceWrapper, node, count, args);
}
throw e;
} catch (Throwable e) {
// Unexpected internal error, set error to current entry.
context.getCurEntry().setError(e);
throw e;
}
}
// com.alibaba.csp.sentinel.node.DefaultNode#increaseThreadNum
@Override
public void increaseThreadNum() {
super.increaseThreadNum();
this.clusterNode.increaseThreadNum();
}
// com.alibaba.csp.sentinel.node.DefaultNode#addPassRequest
@Override
public void addPassRequest(int count) {
super.addPassRequest(count);
this.clusterNode.addPassRequest(count);
}
StatisticSlot 不同于之前的几个 Slot,它的 exit 函数是有实际的任务处理过程,而前面几个 Slot 的 exit 函数只是保证链式调用。
从 AuthoritySlot 开始就到了 Sentinel 根据规则进行检查的阶段了,AuthoritySlot 是其中最简单的部分,它只做白名单和黑名单的检查。这里,首先会确认一下当前资源是否定义了授权规则,如果定义了的话,会挨个检查所有授权规则是否通过,如果发现了任意一个未通过的授权规则就抛出 AuthorityException
,否则调用下一个 Slot。
// com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
throws Throwable {
checkBlackWhiteAuthority(resourceWrapper, context);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
// com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot#checkBlackWhiteAuthority
void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();
if (authorityRules == null) {
return;
}
Set<AuthorityRule> rules = authorityRules.get(resource.getName());
if (rules == null) {
return;
}
for (AuthorityRule rule : rules) {
if (!AuthorityRuleChecker.passCheck(rule, context)) {
throw new AuthorityException(context.getOrigin(), rule);
}
}
}
授权规则的检查过程也很简单,就是检查当前流量的 Origin 是否包含在设定的授权规则名单中,如果这个名单是黑名单并且当前流量的 Origin 在这个名单中,则返回不通过。另外,如果这个名单是白名单但是当前流量的 Origin 不在这个名单中,也返回不通过。
// com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleChecker#passCheck
static boolean passCheck(AuthorityRule rule, Context context) {
String requester = context.getOrigin();
// Empty origin or empty limitApp will pass.
if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
return true;
}
// Do exact match with origin name.
int pos = rule.getLimitApp().indexOf(requester);
boolean contain = pos > -1;
if (contain) {
boolean exactlyMatch = false;
String[] appArray = rule.getLimitApp().split(",");
for (String app : appArray) {
if (requester.equals(app)) {
exactlyMatch = true;
break;
}
}
contain = exactlyMatch;
}
int strategy = rule.getStrategy();
if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
return false;
}
if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
return false;
}
return true;
}
SystemSlot 是对系统规则的检查,只检查输入性流量,检查项主要包括:
Entry#exit
是进行统计即可,后续我们会详细介绍这部分内容。这里也是通过系统统计节点(Constants.ENTRY_NODE)的数据进行检查线程数
和 QPS*RT
的大小,如果 线程数 > QPS*RT
就限流// com.alibaba.csp.sentinel.slots.system.SystemSlot#entry
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
SystemRuleManager.checkSystem(resourceWrapper);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
// com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem
public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
if (resourceWrapper == null) {
return;
}
// Ensure the checking switch is on.
if (!checkSystemStatus.get()) {
return;
}
// for inbound traffic only
if (resourceWrapper.getEntryType() != EntryType.IN) {
return;
}
// total qps
double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();
if (currentQps > qps) {
throw new SystemBlockException(resourceWrapper.getName(), "qps");
}
// total thread
int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
if (currentThread > maxThread) {
throw new SystemBlockException(resourceWrapper.getName(), "thread");
}
double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
if (rt > maxRt) {
throw new SystemBlockException(resourceWrapper.getName(), "rt");
}
// load. BBR algorithm.
if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
if (!checkBbr(currentThread)) {
throw new SystemBlockException(resourceWrapper.getName(), "load");
}
}
// cpu usage
if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
throw new SystemBlockException(resourceWrapper.getName(), "cpu");
}
}
// com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkBbr
private static boolean checkBbr(int currentThread) {
if (currentThread > 1 &&
currentThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) {
return false;
}
return true;
}
当调用点的任务执行过程中出现了异常,我们需要通过 Tracer#trace
记录异常,它本质上就是将异常保存在当前调用点 Entry 中,在保存之前还会对异常的类型进行一些检查(用户可以指定忽略一部分异常),这部分的代码如下。
// com.alibaba.csp.sentinel.Tracer
public static void trace(Throwable e) {
traceContext(e, ContextUtil.getContext());
}
public static void traceContext(Throwable e, Context context) {
if (!shouldTrace(e)) {
return;
}
if (context == null || context instanceof NullContext) {
return;
}
traceEntryInternal(e, context.getCurEntry());
}
protected static boolean shouldTrace(Throwable t) {
if (t == null || t instanceof BlockException) {
return false;
}
// 用户注入的检查器
if (exceptionPredicate != null) {
return exceptionPredicate.test(t);
}
// 用户添加的黑名单
if (ignoreClasses != null) {
for (Class<? extends Throwable> clazz : ignoreClasses) {
if (clazz != null && clazz.isAssignableFrom(t.getClass())) {
return false;
}
}
}
// 用户添加的白名单
if (traceClasses != null) {
for (Class<? extends Throwable> clazz : traceClasses) {
if (clazz != null && clazz.isAssignableFrom(t.getClass())) {
return true;
}
}
return false;
}
return true;
}
private static void traceEntryInternal(/*@NeedToTrace*/ Throwable e, Entry entry) {
if (entry == null) {
return;
}
entry.setError(e);
}
从上面的代码中可以看出 Tracer#trace
先会检查一下是否应该追踪该异常,检查依据是用户添加到 Tracer 中的检查器 exceptionPredicate
、黑名单 ignoreClasses
、白名单traceClasses
,检查通过就将异常保存在当前调用点 Entry 中。而异常的统计工作是在调用 Entry#exit
时才会开始进行,最终在 StatisticSlot
中进行实际的统计工作。
退出调用点的处理流程如下:
ContextUtil#exit
函数,将 Context 实例从 ThreadLocal 中清除@Override
public void exit(int count, Object... args) throws ErrorEntryFreeException {
trueExit(count, args);
}
@Override
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
exitForContext(context, count, args);
return parent;
}
protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
if (context != null) {
// Null context should exit without clean-up.
if (context instanceof NullContext) {
return;
}
if (context.getCurEntry() != this) {
String curEntryNameInContext = context.getCurEntry() == null ? null : context.getCurEntry().getResourceWrapper().getName();
// Clean previous call stack.
CtEntry e = (CtEntry)context.getCurEntry();
while (e != null) {
e.exit(count, args);
e = (CtEntry)e.parent;
}
String errorMessage = String.format("The order of entry exit can't be paired with the order of entry"
+ ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext, resourceWrapper.getName());
throw new ErrorEntryFreeException(errorMessage);
} else {
if (chain != null) {
chain.exit(context, resourceWrapper, count, args);
}
// Restore the call stack.
context.setCurEntry(parent);
if (parent != null) {
((CtEntry)parent).child = null;
}
if (parent == null) {
// Default context (auto entered) will be exited automatically.
if (ContextUtil.isDefaultContext(context)) {
ContextUtil.exit();
}
}
// Clean the reference of context in current entry to avoid duplicate exit.
clearEntryContext();
}
}
}
protected void clearEntryContext() {
this.context = null;
}
[1] Sentinel GitHub 仓库
[2] Sentinel 官方 Wiki
[3] Sentinel 1.6.0 网关流控新特性介绍
[4] Sentinel 微服务流控降级实践
[5] Sentinel 1.7.0 新特性展望
[6] Sentinel 为 Dubbo 服务保驾护航
[7] 在生产环境中使用 Sentinel
[8] Sentinel 与 Hystrix 的对比
[9] 大流量下的服务质量治理 Dubbo Sentinel初涉
[10] Alibaba Sentinel RESTful 接口流控处理优化
[11] 阿里 Sentinel 源码解析
[12] Sentinel 教程 by 逅弈
[13] Sentinel 专题文章 by 一滴水的坚持