一, Sentinel里的ProcessorSlot责任链
上一文里我们介绍了Sentinel里的七种ProcessorSlot,它们分成两类,一类进行数据统计,一类实现降级功能。Sentinel整体工作流程是使用责任链模式将所有的ProcessorSlot按照一定的顺序组成一个单向链表,辅助完成资源指标数据统计的 ProcessorSlot 必须在实现降级功能的 ProcessorSlot 的前面,原因很简单,降级功能需要依据资源的指标数据做判断,当然,如果某个 ProcessorSlot 不依赖指标数据实现降级功能,那这个 ProcessorSlot 的位置就没有约束。
实现将 ProcessorSlot 串成一个单向链表的是 ProcessorSlotChain,这个 ProcessorSlotChain 是由 SlotChainBuilder 构造的,默认 SlotChainBuilder 构造的 ProcessorSlotChain 注册的 ProcessorSlot 以及顺序如下代码所示。
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new LogSlot());
chain.addLast(new StatisticSlot());
chain.addLast(new AuthoritySlot());
chain.addLast(new SystemSlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());
return chain;
}
}
如果是自定义的ProcessorSlot可以通过AbstractLinkedProcessorSlot进行构建
public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
// 当前节点的下一个节点
private AbstractLinkedProcessorSlot<?> next = null;
public void setNext(AbstractLinkedProcessorSlot<?> next) {
this.next = next;
}
@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
throws Throwable {
if (next != null) {
T t = (T) obj;
// 调用下一个 ProcessorSlot 的 entry 方法
next.entry(context,resourceWrapper,t,count,prioritized,args);
}
}
@Override
public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
if (next != null) {
// 调用下一个 ProcessorSlot 的 exit 方法
next.exit(context, resourceWrapper, count, args);
}
}
}
ProcessorSlot 接口的定义如下:
public interface ProcessorSlot<T> {
// 入口方法
void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,Object... args) throws Throwable;
// 调用下一个 ProcessorSlot#entry 方法
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);
// 调用下一个 ProcessorSlot#exit 方法
void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
}
主要方法参数:
- context:当前调用链路上下文。
- resourceWrapper:资源 ID。
- param:泛型参数,一般用于传递 DefaultNode。
- count:Sentinel 将需要被保护的资源包装起来,这与锁的实现是一样的,需要先获取锁才能继续执行。而 count 则与并发编程 AQS 中 tryAcquire 方法的参数作用一样,count 表示申请占用共享资源的数量,只有申请到足够的共享资源才能继续执行。例如,线程池有 200 个线程,当前方法执行需要申请 3 个线程才能执行,那么 count 就是 3。count 的值一般为 1,当限流规则配置的限流阈值类型为 threads 时,表示需要申请一个线程,当限流规则配置的限流阈值类型为 qps 时,表示需要申请 1 令牌(假设使用令牌桶算法)。
- prioritized:表示是否对请求进行优先级排序,SphU#entry 传递过来的值是 false。
- args:调用方法传递的参数,用于实现热点参数限流。
二,Sentinel整体工作流程
下面是不借助 Sentinel 提供的适配器示例
ContextUtil.enter("上下文名称,例如:sentinel_spring_web_context");
Entry entry = null;
try {
entry = SphU.entry("资源名称,例如:/rpc/openfein/demo", EntryType.IN (或者 EntryType.OUT));
// 执行业务方法
return doBusiness();
} catch (Exception e) {
if (!(e instanceof BlockException)) {
Tracer.trace(e);
}
throw e;
} finally {
if (entry != null) {
entry.exit(1);
}
ContextUtil.exit();
}
流程为5步:
- 调用 ContextUtil#enter 方法。负责为当前调用链路创建 Context,创建好后存入ThreadLocal,以及为 Conetxt 创建 EntranceNode
- 调用 SphU#entry 方法。这里整体链路里的核心调用,CtSph 负责为资源创建 ResourceWrapper 对象并为资源构造一个全局唯一的 ProcessorSlotChain、为资源创建 CtEntry 并将 CtEntry 赋值给当前调用链路的 Context.curEntry、最后调用 ProcessorSlotChain#entry 方法完成一次单向链表的 entry 方法调用。
- 如果抛出异常,且异常类型非 BlockException 异常,则调用 Tracer#trace 方法记录异常,为当前资源的 DefaultNode 自增异常数
- 调用 Entry#exit 方法;
- 调用 ContextUtil#exit 方法。
关于其中第2点再补充一下:
调用了entry方法后,先进入到FlowSlot的entry方法进行限流过滤:
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
checkFlow(resourceWrapper, context, node, count, prioritized);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
然后再看对应的checkFlow方法:
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
if (ruleProvider == null || resource == null) {
return;
}
Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
if (rules != null) {
for (FlowRule rule : rules) {
if (!canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp(), rule);
}
}
}
}
这里拿到了我们设置的 FlowRule 循环匹配资源进行限流过滤。这就是Sentinel 能做到限流的原因。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。