1、核心概念
1.1、资源-Resource
- Resource:Sentinel中最核心的概念,其通过资源来保护具体的业务代码或其他后方服务,用户只需要将代码或者服务定义为一个资源,然后再定义规则就可以了,剩下的交给sentinel来处理。并且资源和规则时解耦的,规则可在运行是动态修改;定义完资源后,就可以通过在程序中埋点来保护你自己的服务了,埋点的方式有两种:
- try…catch的方式【通过
SphU.entry(...)
】:当catch到BlockException时,执行异常处理或者fallback。 - if…else的方式【通过
SphO.entry(...)
】:当返回false时执行异常处理或者fallback。其实内部是将ry…catch的方式抛出的异常BlockException
进行包装,有异常返回false。
- try…catch的方式【通过
//SphO.entry(...)
public static boolean entry(String name, EntryType type, int count, Object... args) {
try {
Env.sph.entry(name, type, count, args);
} catch (BlockException e) {
return false;
} catch (Throwable e) {
RecordLog.info("[Sentinel] Fatal error", e);
return true;
}
return true;
}
//SphU.entry(...)
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
return Env.sph.entry(name, type, count, args);
}
-
用户采用Sph…的方式定义资源对业务代码侵入很大,所以Sentinel提供了注解式【SentinelResource**】**来定义资源,并定义资源对应异常处理
blockHandler
。 -
Sentinel中资源对应的类为ResourceWrapper,其为抽象的包装类,包装了资源的 Name 和**EntryType,**具体有两个实现类,分别是:
StringResourceWrapper
和MethodResourceWrapper``。
- StringResourceWrapper:是对String对一串字符串进行包装,是一个通用的资源包装类。
- MethodResourceWrapper: 是对方法调用的包装。
1.2、槽-Slot
Sentinel的工作流程是围绕着一个个插槽所组成的槽链来实现的。Sentinel默认的各个插槽之间的顺序是固定的,因为有的插槽实现需要依赖其他插槽的数据统计。Sentinel也提供用户自定义方式实现插槽,但则只能放在默认Slot链的尾部。如果用户想自定义Slot链,可以实现**SlotChainBuilder**
来自定义Slot的顺序编排。
- Slot Chain:DefaultProcessSlotChain(first) -> NodeSelectorSlot -> ClusterBuilderSlot -> LogSlot -> StatisticSlot -> AuthoritySlot -> SystemSlot -> FlowSlot -> DegradeSlot(end)
- NodeSelectorSlot:负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。
- ClusterBuilderSlot:用于存储资源的统计信息以及调用信息
- LogSlot:用于记录日志信息
- StatisticSlot:用于记录不同维度的runtime信息,Pass、Block、
- AuthoritySlot:根据黑白名单来进行控制
- SystemSlot:通过系统状态来控制总的入口流量
- FlowSlot:用于根据预设的限流规则,以及前面的slot统计状态,来进行限流
- DegradeSlot:通过统计信息以及预设的规则,来进熔断降级。
- SlotChain初始化**->执行链路:**
- SlotChain初始化:CtSph的lookProcessChain,该方法会根据资源先从一个静态Map中获取SlotChain,若取不到才会进行初始化。这也表明同一个资源会全局共享一个SlotChain。
1.3、上下文-Context
This class holds metadata of current invocation。记录当前调用链的元数据,元数据包含:
- entranceNode:当前调用链的入口节点
- curEntry:调用链当前处理实体
- origin:当前调用链的调用源,如服务消费者名称或者源IP
每次SphU.entry()
或 SphO.entry()
的调用流程需要在一个Context中执行,如果当前执行时还没有 context,那么框架会使用默认的 context,默认的 context 是通过 MyContextUtil.myEnter()
创建。若用户想自定义Context,调用ContextUtil.enter来实现。
- NodeSelectorSlot中的map,key为context的name而不是资源的name,原因是为了同一个资源多次调用,对应不同的Context,在各自执行完成后,再通过ClusterNode来进行合并。
- 【资源调用链路??】同一个context中对同一个resource进行多次entry()调用时,会形式一颗调用树,这个树是通过CtEntry之间的parent/child关系维护的。
## 1.4、凭证-Entry **Entry**是Sentinel用来标识是否进行限流降级的一个凭证,类似于一个token。每次执行`SphU.entry()` 或 `SphO.entry()`时都会返回一个Entry实体给调用者。告诉调用者如果正确返回了 `Entry`,那表示可以正常访问被sentinel保护的后方服务了,否则sentinel会抛出一个BlockException(如果是 `SphO.entry()`会返回false),这就表示调用者想要访问的服务被保护,即调用者本身被限流了。
-
Entry中包含了当前执行的一些基本信息:
- createTime:创建Entry实体的时间,主要后面用于计算rt
- curNode:当前执行节点
- originNode:当前执行流程源节点,通常是调用方的应用名称
- error:
- resourceWrapper:当前Entry关联的资源
-
当在一个上下文中多次调用了
SphU.entry()
方法时,就会创建一个调用树,这个树的节点之间是通过parent和child关系维持的。需要注意的是:parent和child是在CtSph
类的一个私有内部类CtEntry
中定义的,CtEntry
是Entry
的一个子类。 -
** SphU.entry 方法的第二个参数 EntryType 说的是这次请求的流量类型,共有两种类型:IN 和 OUT 。**
- IN:是指进入我们系统的入口流量,比如 http 请求或者是其他的 rpc 之类的请求。
- OUT:是指我们系统调用其他第三方服务的出口流量。
1.5、节点-Node
Node中保存了资源的实时统计数据,入passQps、blockQps、Rt等信息。Sentinel也是依据于这些数据才能实现限流降级等功能。Node的唯一统计实现类为StatisticNode。
- EntranceNode是每个上下文的入口节点类,该节点是直接挂在root下的,是全局唯一的,每一个context都会对应一个entranceNode。
public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN),
new ClusterNode());
- DefaultNode是记录当前调用的实时数据的,每个defaultNode都关联着一个资源和clusterNode,有着相同资源的defaultNode,他们关联着同一个clusterNode。
1.6、度量-Metric
Metric是sentinel中用来进行实时数据统计的度量接口,Node就是通过Metric来进行数据统计的。而Metric本身也并没有统计的能力,他也是通过Window来进行统计的。
Metric的实现类ArrayMetric,其通过LeapArray来实现对窗口的统计。
2、Sentinel调用链
Sentinel的一堆Slot构成了调用链路,从SphU.entry或者Sph0.entry开始,到lookProcessChain初始化slotchain,再调用ProcessorSlot的entry进入slot调用链。
每个Slot执行完逻辑后,都会调用fireEntry()方法,会触发下一个节点的entry()方法,下一个节点又会调用他的fireEntry,以此类推直到最后一个Slot,由此就形成了sentinel的责任链。
每次链调用一个资源,关联着一个Context,每个Context包含entranceNode、curEntry、originNode(名字)
- 再提一下几个核心的角色:
- Context
- Entry
- Node
2.1、Context创建与销毁
- 创建:Context的创建由ContextUtil.trueEnter来实现。
protected static Context trueEnter(String name, String origin) {
//从ThreadLocal中获取Context
Context context = contextHolder.get();
if (context == null) {
//如果ThreadLocal中获取不到Context,则根据contextName从map中获取根节点,只要是相同的资源名,就能直接从map中获取到Node(入口节点)
Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
DefaultNode node = localCacheNameMap.get(name);
if (node == null) {
//当然context还没有入口节点,则判断已存在的个数大小,或者重新创建
if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
setNullContext();
return NULL_CONTEXT