Sentinel源码学习笔记

官网上的架构图
在这里插入图片描述
Sentinel 的核心部分为 ProcessorSlotChain ,由图可知是由将不同的 slot 按照指定顺序串在一起(责任链模式),通过此过滤链可以将监控统计、限流、熔断降级等功能整合在一起。系统会为每一个资源创建一套 SlotChain!
ps:使用@SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理

分析

在springboot中会有自动配置功能,通过查看spring.factories文件找到 SentinelAutoConfiguration类
此类中,通过下面方法来实现了 Sentinel最核心的功能
在这里插入图片描述

通过切面进行方法增强

点进去查看
在这里插入图片描述

此类是利用AspectJ 来实现 aop 增强,对 @SentinelResource标注的资源进行加强!Entry 可以是可以操作资源的对象

创建Entry对象

进入红框内的方法,经过一系列ctrl + 左键进入到如下方法

在这里插入图片描述
从上图可以总结出,Entry对象的创建有两大步:1. 创建 Context 2. 创建 ProcessorSlot
让我们再来重点看看这两个方法

创建 Context 对象

protected static Context trueEnter(String name, String origin) {
        // 尝试着从ThreadLocal中获取Context
        Context context = contextHolder.get();
        // 若ThreadLocal中没有context,则尝试着从缓存map中获取
        if (context == null) {
            // 缓存map的key为context名称,value为EntranceNode
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            // 获取EntranceNode——双重检测锁DCL——为了防止并发创建
            DefaultNode node = localCacheNameMap.get(name);
            if (node == null) {
                // 若缓存map的size 大于 context数量的最大阈值,则直接返回NULL_CONTEXT
                if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                    setNullContext();
                    return NULL_CONTEXT;
                } else {
                    LOCK.lock();
                    try {
                        node = contextNameNodeMap.get(name);
                        if (node == null) {
                            if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                                setNullContext();
                                return NULL_CONTEXT;
                            } else {
                                // 创建一个EntranceNode
                                node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                // Add entrance node.将新建的node添加到ROOT
                                Constants.ROOT.addChild(node);

                                // 将新建node写入到缓存map
                                // 为了防止“迭代稳定性问题”——iterate stable——对于共享集合的写操作
                                Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                                newMap.putAll(contextNameNodeMap);
                                newMap.put(name, node);
                                contextNameNodeMap = newMap;
                            }
                        }
                    } finally {
                        LOCK.unlock();
                    }
                }
            }
            // 将context的name与entranceNode封装为context
            context = new Context(node, name);
            // 初始化context的来源
            context.setOrigin(origin);
            // 将context写入到ThreadLocal
            contextHolder.set(context);
        }

        return context;
    }

重点看一下 DCL 机制 和 如何解决集合迭代性问题

创建 ProcessorSlot

    ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
        // 从缓存map中获取当前资源的SlotChain
        // 缓存map的key为资源,value为其相关的SlotChain
        ProcessorSlotChain chain = chainMap.get(resourceWrapper);
        // DCL
        // 若缓存中没有相关的SlotChain,则创建一个并放入到缓存
        if (chain == null) {
            synchronized (LOCK) {
                chain = chainMap.get(resourceWrapper);
                if (chain == null) {
                    // Entry size limit.
                    // 缓存map的size >= chain数量最大阈值,则直接返回null,不再创建新的chain
                    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.newSlotChain(); **

    public static ProcessorSlotChain newSlotChain() {
        // 若builder不为null,则直接使用builder构建一个chain,否则先创建一个builder
        if (slotChainBuilder != null) {
            return slotChainBuilder.build();
        }

        // Resolve the slot chain builder SPI.
        // 通过SPI方式创建一个builder
        slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();

        // 若通过SPI方式未能创建builder,则手工new一个DefaultSlotChainBuilder
        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());
        }
        // 构建一个chain
        return slotChainBuilder.build();
    }

通过 SPI 机制来加载指定的对象

接下来,开始执行操作(即在上面创建 Entry步骤中)

在这里插入图片描述
参考责任链模式,就是通过一个链表把不同 Slot 串联起来。

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

滑动时间窗算法

先了解一下固定的时间窗算法
在这里插入图片描述
该算法原理是,系统会自动选定一个时间窗口的起始零点,然后按照固定长度将时间轴划分为若干定长的时间窗口。
当请求到达时,系统会查看请求到达的时间点所在的时间窗口,其统计的数据是否超出了预定的阈值。未超出,则请求通过,否则被限流
存在的问题
在这里插入图片描述
跨窗口的时间窗长度范围内统计的数据却超出了阈值。这就是滑动时间窗要解决的问题

算法原理
滑动时间窗限流算法并没有划分固定的时间窗起点与终点,而是将每一次请求到来的时间点作为统计时间窗的终点,起点则是终点向前推时间窗长度的时间点。
在这里插入图片描述

算法改进:将时间窗口拆分为若干固定长度的样本窗口在这里插入图片描述
这样每个样本窗口内统计值其对应时间段内的流量数据,可以提高数据的重复利用率

一些比较重要的概念

  • context:表示资源的上下文信息。一个资源在不同的context中可能会有不同的规则
  • Node:用于完成数据统计的接口
  • entry 操作资源的对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值