乐于分享
好东西不私藏

手撕Sentinel源码:从ProcessorSlotChain责任链看懂限流熔断底层逻辑

手撕Sentinel源码:从ProcessorSlotChain责任链看懂限流熔断底层逻辑

线上跑了半年的Sentinel限流规则突然不生效?加了降级配置请求还是被打满?90%的问题都出在你没搞懂ProcessorSlotChain的执行逻辑,本文从源码层扒透责任链设计,让你从此排查Sentinel问题效率提升10倍。

一、先搞懂:ProcessorSlotChain到底是Sentinel的核心骨架

Sentinel所有的限流、降级、权限校验能力,本质上都是靠责任链模式串起来的一个个处理器完成的,ProcessorSlotChain就是承载所有能力的核心执行骨架。

首先我们看核心类结构,ProcessorSlotChain本身继承了AbstractLinkedProcessorSlot,内部维护了一个双向链表结构,支持在头部或尾部插入Slot处理器:

// 核心责任链抽象类
public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot<Object> {
    // 在链头部插入处理器
    public abstract void addFirst(ProcessorSlot<?> protocolProcessor);
    // 在链尾部插入处理器
    public abstract void addLast(ProcessorSlot<?> protocolProcessor);
}

责任链的加载逻辑完全基于SPI实现,默认的DefaultSlotChainBuilder会加载classpath下META-INF/services目录中配置的所有ProcessorSlot实现类,并且按照@Order注解的值从小到大排序后插入链表:

// SPI加载Slot核心实现
public class DefaultSlotChainBuilder implements SlotChainBuilder {
    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        // 加载所有SPI配置的ProcessorSlot并按Order排序
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class)
            .loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            chain.addLast(slot);
        }
        return chain;
    }
}

这里要注意一个核心点:@Order值越小,Slot的执行优先级越高,官方默认的Slot顺序就是严格按照执行依赖关系排序的,一旦顺序被打乱就会出现规则不生效的问题。

二、逐行拆解:责任链全流程执行逻辑

整个Sentinel的请求处理流程可以完全对应下面的UML流程图,每个Slot各司其职,只要有一个Slot校验不通过,整个请求就会被直接拦截:

我们逐个拆解核心Slot的作用:
1. NodeSelectorSlot:负责维护资源的调用路径,将同一个资源的不同调用链路分开统计,是链路流控的核心基础
2. ClusterBuilderSlot:负责构建集群维度的统计节点,存储资源的集群QPS、线程数、响应时间等核心指标
3. LogSlot:负责记录所有规则校验不通过的异常日志,方便后续排查问题
4. StatisticSlot:是整个统计体系的核心,负责在请求通过、被拦截、抛出异常的时候更新对应的统计指标,后续所有规则校验都依赖这里统计的数据
5. AuthoritySlot:负责黑白名单权限校验,判断请求来源是否允许访问当前资源
6. SystemSlot:负责系统级别的流控,校验当前系统的CPU、负载、平均响应时间等指标是否超过阈值
7. ParamFlowSlot:负责热点参数限流,针对请求中的特定参数做更细粒度的流控
8. FlowSlot:核心的流量控制校验,根据配置的限流规则判断当前请求是否允许通过
9. DegradeSlot:核心的降级熔断校验,根据慢调用比例、异常比例等规则判断是否需要熔断当前资源

我们看FlowSlot的核心校验逻辑代码:

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args) throws Throwable {
    // 调用流控规则校验逻辑
    checkFlow(resourceWrapper, context, node, count, prioritized);
    // 校验通过 传递给下一个Slot
    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
    throws BlockException {
    // 获取当前资源配置的所有流控规则
    List<FlowRule> rules = FlowRuleManager.getRulesForResource(resource.getName());
    if (rules == null || rules.isEmpty()) {
        return;
    }
    for (FlowRule rule : rules) {
        // 逐个校验规则 只要有一个不通过就抛出FlowException
        if (!ruleChecker.canPass(rule, context, node, count, prioritized)) {
            throw new FlowException(rule.getLimitApp(), rule);
        }
    }
}

这里可以看到,只要有一个规则校验不通过,就会直接抛出异常,不会走到后续的Slot和业务逻辑。

三、踩坑必看:90%的规则失效问题都是这些原因

  1. 自定义Slot顺序配置错误:很多同学自定义Slot的时候随便写@Order值,如果Order值比FlowSlot的Order(5000)还大,就会出现请求已经走到业务逻辑了才校验限流规则,完全不生效
  2. 禁用了核心统计Slot:如果禁用了StatisticSlot,所有的指标统计都会失效,后续的限流、降级规则因为没有统计数据完全不会触发
  3. 多上下文链路统计隔离:如果没有配置正确的上下文,同一个资源的不同链路统计会混在一起,导致链路流控不生效

好了今天的源码拆解就到这,是不是看完终于搞懂Sentinel为啥规则有时候莫名其妙不生效了?你们平时用Sentinel还踩过啥奇葩坑?评论区唠唠,我整理下下期出完整踩坑大全~

相关学习资料