7. Sentinel SlotChain的创建和调用源码解析

7. Sentinel SlotChain的创建和调用源码解析

1. 入口简介

通过5小节,我们知道了是同SphU.entry方法来作为流控的入口的,也就是说该方法会执行流控相关逻辑,那么它是如何实现的呢? 通过Sentinel结构图我们知道它里面应该会构建一个调用链,这个调用链就是SlotChain,那么我们接下来着重解析这个调用链。

2. 各种Slot介绍

在 Sentinel里面,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个 Entry 对象,Entry 创建的时候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如默认情况下会创建一下7个插槽。

具体如下:

  • NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
  • ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据,对应簇点链路;
  • StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
  • FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制,对应流控规则;
  • AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制,对应授权规则;
  • DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级,对应熔断规则;
  • SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量,对应系统规则;

3. SlotChain 的创建

我们需要从SphU.entry入手,最终会进入到方法

public class CtSph implements Sph {
  ...
  private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
      throws BlockException {
      ...
      // 获取chain链
      ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
      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;
  }
  ...
}

通过代码可以看出,我们在entryWithPriority方法中会获取调用链,通过chain的entry方法来实现对链的调用执行。

所以lookProcessChain方法是比较重要的,它是如何创建并获取到这个调用链的呢,所以我们需要跟踪这个方法的实现。

//CtSph.java
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
        = new HashMap<ResourceWrapper, ProcessorSlotChain>();
...        
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.
                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;
}

从以上代码可以看出一个重要的知识点,就是调用链最终是以资源为Key缓存下来的,也就是说调用链是资源级别的。每个资源有一个自己的调用链。

可以看出重要的方法是:SlotChainProvider.newSlotChain(); 从方法名字来看,应该是创建调用链,进去看看。

public final class SlotChainProvider {
    private static volatile SlotChainBuilder slotChainBuilder = null;
    public static ProcessorSlotChain newSlotChain() {
        if (slotChainBuilder != null) {
            return slotChainBuilder.build();
        }
        slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
        if (slotChainBuilder == null) {
            slotChainBuilder = new DefaultSlotChainBuilder();
        } 
        return slotChainBuilder.build();
    }
}

这里的创建是通过Builder来创建的, 所以先要创建Builder, 可以看到Builder是可以被SpiLoader替换的,如果没有对应的,兜底的Builder类是DefaultSlotChainBuilder,这里的SpiLoader(Service Provider Interface)主要是读取META-INF/services/ 目录下的文件进行映射,如果里面有声明则会使用到。 我们通过源码得知, 在目录中有对应的映射,也是指向了DefaultSlotChainBuilder。 所以DefaultSlotChainBuilder为ProcessorSlotChain 的Builder。

所以我们跟踪到DefaultSlotChainBuilder的build方法

public class DefaultSlotChainBuilder implements SlotChainBuilder {
    @Override
    public ProcessorSlotChain build() {
        //SlotChain的第一个Slot是DefaultProcessorSlotChain从这里可看出
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        //然后又通过SPI加载其他的Slot,从可以看出我们扩展的话可以通过实现ProcessorSlot来加入到Chain中
        //从下图可以看出默认的Slot都有哪些,在META-INF/services/下面的文件中声明了
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            ...
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }
        return chain;
    }
}


# Sentinel default ProcessorSlots
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
com.alibaba.csp.sentinel.slots.logger.LogSlot
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
com.alibaba.csp.sentinel.slots.system.SystemSlot
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot

这里也就和我们架构图中的调用链对应上了

4. SlotChain的调用

让我们再回到CtSph.entryWithPriority方法中,以上解析,当方法CtSph.lookProcessChain调用完成后我们就获得了调用链,有了调用链后,就会对它进行逐个进行调用,具体实现如下:

//CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
      throws BlockException {
      ...
      ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
      ...
      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;
  }

通过链头节点调用chain.entry方法,从之前的分析来看链头应该是DefaultProcessorSlotChain,所以调用后应该会进入DefaultProcessorSlotChain的entry方法。链中的每一个节点执行完自己内部逻辑后,要负责调用下一个节点的entry方法。

//DefaultProcessorSlotChain.java
AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
        throws Throwable {
        //这里调用了父类的AbstractLinkedProcessorSlot.fireEntry方法
        super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
    }
    ...
};
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
    throws Throwable {
    //调用first的transformEntry方法,从而调用了父类AbstractLinkedProcessorSlot的fireEntry
    first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
}

//AbstractLinkedProcessorSlot.java
@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
    throws Throwable {
    if (next != null) {
        //调用了下一个节点的transformEntry方法,里面就会调用entry方法
        next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
    }
}

void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
    throws Throwable {
    T t = (T)o;
    //调用entry方法,执行本Slot的内部业务逻辑,并且触发调用下一个节点的entry方法
    entry(context, resourceWrapper, t, count, prioritized, args);
}

至此我们调用链创建完成并,开始调用了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值