@SentinelResource属性:
- value
资源名称,默认为"",为""时,自动生成资源名,生成规则为className:methodName(paramName2,paramName2,...) - entryType
entry类型,默认为EntryType.OUT(出口流量,即对本接口调用其它接口进行限流或熔断等),还可以设置为EntryType.IN(入口流量,即对外部应用调用本应用接口进行限流或熔断等) - resourceType
默认为0,值枚举如下:
0:COMMON
1:COMMON_WEB
2:COMMON_RPC
3:COMMON_API_GATEWAY
4:COMMON_DB_SQL - blockHandler
对应处理 BlockException 的函数名称,默认为"" - blockHandlerClass
blockHandler函数所在的类,默认为{},只能提供一个类;如果提供blockHandlerClass,blockHandler函数必须是static方法。 - fallback
降级函数名称,默认为"",用于在抛出异常的时候提供 fallback 处理逻辑,fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。
(1)返回值类型必须与原函数返回值类型一致;
(2)方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
(3)fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 - defaultFallback
通用的降级函数名称(即可以用于很多服务或方法),默认为"",默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效,defaultFallback 函数签名要求:
(1)返回值类型必须与原函数返回值类型一致;
方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
(2)defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 - fallbackClass
- exceptionsToTrace
指定哪些异常类需要计入异常统计,默认值为{Throwable.class} - exceptionsToIgnore
用于指定哪些异常被排除掉,默认值为{},不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出,exceptionsToTrace与exceptionsToIgnore不应该同时出现,如果同时出现,exceptionsToIgnore优先级更高。
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出。
public class TestService {
// 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}
// 原函数
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
return String.format("Hello at %d", s);
}
// Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}
// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}
}
规则
image.png image.png image.png请求入口:
SentinelResourceAspect#invokeResourceWithSentinel->SphU#entry->CtSph#entryWithType->CtSph#entryWithPriority
public class CtSph implements Sph {
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, 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, 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;
}
}
核心类包括Entry、Node、Context:
image.pngContext
Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry
。Context 维持着入口节点(entranceNode
)、本次调用链路的 curNode、调用来源(origin
)等信息。Context 名称即为调用链路入口名称。
Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter
的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, f)
来变换 context。
Entry
每一次资源调用都会创建一个 Entry
。Entry
包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。
CtEntry
为普通的 Entry
,在调用 SphU.entry(xxx)
的时候创建。特性:Linked entry within current context(内部维护着 parent
和 child
)
需要注意的一点:CtEntry 构造函数中会做调用链的变换,即将当前 Entry 接到传入 Context 的调用链路上(setUpEntryFor
)。
资源调用结束时需要 entry.exit()
。exit 操作会过一遍 slot chain exit,恢复调用栈,exit context 然后清空 entry 中的 context 防止重复调用。
Node
Sentinel 里面的各种种类的统计节点:
- StatisticNode:最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构。
- DefaultNode:链路节点,用于统计调用链路上某个资源的数据,维持树状结构。
- ClusterNode:簇点,用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据(类型为 StatisticNode)。特别地,Constants.ENTRY_NODE 节点用于统计全局的入口资源数据。
- EntranceNode:入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据。Constants.ROOT 节点也是入口节点。
构建的时机:
- EntranceNode 在 ContextUtil.enter(xxx) 的时候就创建了,然后塞到 Context 里面。
- NodeSelectorSlot:根据 context 创建 DefaultNode,然后 set curNode to context。
- ClusterBuilderSlot:首先根据 resourceName 创建 ClusterNode,并且 set clusterNode to defaultNode;然后再根据 origin 创建来源节点(类型为 StatisticNode),并且 set originNode to curEntry。
几种 Node 的维度(数目):
- ClusterNode 的维度是 resource
- DefaultNode 的维度是 resource * context,存在每个 NodeSelectorSlot 的 map 里面
- EntranceNode 的维度是 context,存在 ContextUtil 类的 contextNameNodeMap 里面来源节点(类型为 StatisticNode)的维度是 resource * origin,存在每个 ClusterNode 的 originCountMap 里面
在NodeSelectorSlot中生成DefaultNode对象,通过context.setCurNode(node)设置CtEntry的curNode属性(DefaultNode),即先通过Context.curEntry获取当前Entry再设置。在传递该DefaultNode到下一个Slot,即ClusterBuilderSlot。
在ClusterBuilderSlot中生成ClusterNode对象(只生成一次),把上一步DefaultNode的clusterNode设置为生成ClusterNode;根据context的origin生成StatisticNode对象,context.getCurEntry().setOriginNode(originNode)设置为该StatisticNode对象。
StatisticSlot先传递到责任链上其后的Slot执行,没有Slot触发了阻止资源访问且其后所有Slot都执行完,才在StatisticSlot记录、统计指标监控信息的。
image.png
SphO
SphO.entry方法返回false,代表超过规则阈值
public void foo() {
if (SphO.entry("abc")) {
try {
// business logic
} finally {
SphO.exit(); // must exit()
}
} else {
// failed to enter the protected resource.
}
}
SphU
public void foo() {
Entry entry = null;
try {
entry = SphU.entry("abc");
// resource that need protection
} catch (BlockException blockException) {
// when goes there, it is blocked
// add blocked handle logic here
} catch (Throwable bizException) {
// business exception
Tracer.trace(bizException);
} finally {
// ensure finally be executed
if (entry != null){
entry.exit();
}
}
}
ProcessorSlotChain
Sentinel 的核心骨架,将不同的 Slot 按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级、系统保护)组合在一起。slot chain 其实可以分为两部分:统计数据构建部分(statistic)和判断部分(rule checking)。核心结构:
目前的设计是 one slot chain per resource,因为某些 slot 是 per resource 的(比如 NodeSelectorSlot)
slot在责任链中执行顺序是按照@SpiOrder定义,@SpiOrder对应得值越小越先执行。
Resource/META-INF.services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder配置SPI加载哪个SlotChainBuilder实现类来组装责任链,默认为DefaultSlotChainBuilder。
Resource/META-INF.services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot配置SPI加载哪些Slot实现类。
同一个资源 ResourceWrapper (同一个资源名称) 会共同使用同一个 ProcessorSlotChain ,即不同的线程在访问同一个资源保护的代码时,这些线程将共同使用 ProcessorSlotChain 中的各个 ProcessorSlot。
责任链表里的入口类是DefaultProcessorSlotChain,责任链执行过程:
DefaultProcessorSlotChain#entry->
(first)AbstractLinkedProcessorSlot.transformEntry->
(first)AbstractLinkedProcessorSlot.entry->
(first)AbstractLinkedProcessorSlot.fireEntry->
(next)AbstractLinkedProcessorSlot.transformEntry->
(next)AbstractLinkedProcessorSlot.entry->
(next)AbstractLinkedProcessorSlot.fireEntry->
(next.next)AbstractLinkedProcessorSlot.transformEntry->..
public class DefaultProcessorSlotChain extends ProcessorSlotChain {
AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
throws Throwable {
super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
super.fireExit(context, resourceWrapper, count, args);
}
};
AbstractLinkedProcessorSlot<?> end = first;
@Override
public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {
protocolProcessor.setNext(first.getNext());
first.setNext(protocolProcessor);
if (end == first) {
end = protocolProcessor;
}
}
@Override
public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
end.setNext(protocolProcessor);
end = protocolProcessor;
}
/**
* Same as {@link #addLast(AbstractLinkedProcessorSlot)}.
*
* @param next processor to be added.
*/
@Override
public void setNext(AbstractLinkedProcessorSlot<?> next) {
addLast(next);
}
@Override
public AbstractLinkedProcessorSlot<?> getNext() {
return first.getNext();
}
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
throws Throwable {
first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
}
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
first.exit(context, resourceWrapper, count, args);
}
}
public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
private AbstractLinkedProcessorSlot<?> next = null;
@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
if (next != null) {
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);
}
@Override
public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
if (next != null) {
next.exit(context, resourceWrapper, count, args);
}
}
public AbstractLinkedProcessorSlot<?> getNext() {
return next;
}
public void setNext(AbstractLinkedProcessorSlot<?> next) {
this.next = next;
}
}
image.png
image.png
网友评论