Sentinel源码解析之链式初始化

摘自官网: https://sentinelguard.io/zh-cn/docs/basic-implementation.html

基本概念

在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:

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

初始化入口

初始化主要由sentinel-core来完成,重点关注META-INF/services目录下的配置文件,见下图

默认链式构造器

配置文件

路径:META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder

# Default slot chain builder
com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder

默认槽列表

配置文件

路径:META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot

# 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
com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot

核心构造过程解析

CtSph源码

这里根据资源来获取ProcessorSlotChain,也是查找chain,链式初始化的主入口,然后通过一个chainMap来维护映射关系

注解原文:Same resource({@link ResourceWrapper#equals(Object)}) will share the same * {@link ProcessorSlotChain} globally, no matter in which {@link Context}  。无论在哪个上下文,每个资源共享同一个全局chain

 /**
     * Same resource({@link ResourceWrapper#equals(Object)}) will share the same
     * {@link ProcessorSlotChain}, no matter in which {@link Context}.
     */
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
        = new HashMap<ResourceWrapper, ProcessorSlotChain>();


// 核心入口逻辑
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
    throws BlockException {
    Context context = ContextUtil.getContext();
    
    // 查找chain,链式初始化的主入口
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

    Entry e = new CtEntry(resourceWrapper, chain, context, count, args);
    try {
        // 发起链式调用
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {

    } catch (Throwable e1) {

    }
    return e;
}


// 根据资源获取chain
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则新创建一个
                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;
}

SlotChainProvider源码

通过SpiLoader 找到构造器配置文件META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder,实例化加载其中配置的默认链式构造器com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder

/**
 * The load and pick process is not thread-safe, but it's okay since the method should be only invoked
 * via {@code lookProcessChain} in {@link com.alibaba.csp.sentinel.CtSph} under lock.
 *
 * @return new created slot chain
 */
public static ProcessorSlotChain newSlotChain() {
    if (slotChainBuilder != null) {
        return slotChainBuilder.build();
    }

    // Resolve the slot chain builder SPI.
    slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();

    if (slotChainBuilder == null) {
        // Should not go through here.
        RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
        slotChainBuilder = new DefaultSlotChainBuilder();
    } else {
        RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
            slotChainBuilder.getClass().getCanonicalName());
    }
    return slotChainBuilder.build();
}

DefaultSlotChainBuilder源码

通过ProcessorSlot类路径找到上面配置文件,并加载配置文件中的全部slot

@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        // 创建SpiLoader,并加载配置文件
        // META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot中的槽列表
        // 对chain中的slot进行排序
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();


        // 将排序后的slot逐个加到链中
        for (ProcessorSlot slot : sortedSlotList) {
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue;
            }
            
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }

        return chain;
    }
}

SpiLoader源码

1、根据类名找到对应的配置文件META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot

2、遍历配置文件找到对应的槽为class对象,并根据@SPI order进行排序,每个Slot都有@SPI注解

3、下面是精简过的load代码,为了方便理解去掉了一些非核心的判断检查逻辑

/**
 * Load the Provider class from Provider configuration file
 */
public void load() {
    // 配置文件路径,即:META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
    String fullFileName = SPI_FILE_PREFIX + service.getName();
    ClassLoader classLoader;
   
    Enumeration<URL> urls = null;
    

    // 获取配置文件url 
    try {
        urls = classLoader.getResources(fullFileName);
    } catch (IOException e) {
        fail("Error locating SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader, e);
    }

    
    // 遍历配置文件url
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();

        InputStream in = null;
        BufferedReader br = null;
        try {
            in = url.openStream();
            br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
            String line;
            while ((line = br.readLine()) != null) {
                
                // 逐行读取配置文件
                line = line.trim();
           
                // 根据配置文件中每行的类路径找到类对象class,并添加到classlist 中
                Class<S> clazz = null;
                try {
                    clazz = (Class<S>) Class.forName(line, false, classLoader);
                } catch (ClassNotFoundException e) {
                    fail("class " + line + " not found", e);
                }

                classList.add(clazz);
                
            }
        } catch (IOException e) {
            fail("error reading SPI configuration file", e);
        } finally {
            closeResources(in, br);
        }
    }

    // 根据@SPI注解中的order进行排序slot链,从小到大排序
    sortedClassList.addAll(classList);
    Collections.sort(sortedClassList, new Comparator<Class<? extends S>>() {
        @Override
        public int compare(Class<? extends S> o1, Class<? extends S> o2) {
            Spi spi1 = o1.getAnnotation(Spi.class);
            int order1 = spi1 == null ? 0 : spi1.order();

            Spi spi2 = o2.getAnnotation(Spi.class);
            int order2 = spi2 == null ? 0 : spi2.order();

            return Integer.compare(order1, order2);
        }
    });
}

4、默认的顺序源码,可以根据名字猜猜对应的都是哪个slot

总结

Sentinel链式初始化基本完成,核心用到了SPI技术能够自动启动初始化逻辑并支持扩展

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值