Sentinel源码解析

一、Sentinel工作原理

  • 工作流程:一个请求发送,会贯穿多个资源有序进行,最终对不同资源会有对用的插槽,进行流量监控和限制等操作

  • 架构图解析

    • 核心骨架(ProcessorSloatChain):将不同的slot按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级和保护)组合到一起,系统会为每一套资源创建一个slotChain
    • slotChain分为两部分
      • 统计数据构建(statistic)
      • 判断部分(rule checking)
        在这里插入图片描述
  • SPI机制

    • Sentinel中各slot的执行顺序是固定的。Sentinel将ProcessorSlot作为SPI的接口进行扩展,使得SloatChain具备了扩展能力,用户可以自定义slot并插入到固定的slot的任意位置
  • 在 Sentinel 里面,每一个资源都对应一个资源名称和Entry。

  • Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建

  • 每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责

    • Slot介绍:用于对资源的流量监控、限制和降级
      • NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
      • ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
      • StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
      • FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
      • AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
      • DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
      • SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量;
    • Context介绍:贯穿于整个请求的资源的上下文
      • 每一个资源必须属于一个上下文,如果没有创建,则会自动创建一个name为sentinel_default_context的默认context
      • Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。Context 维 持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息。Context 名称即为调用链路入口名称。
        • Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter 的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, from) 来变换 context。
        • ContextUtil.runOnContext(context, from):创建一个来自from资源的访问的上下文。
          • from:请求来源的上级资源服务名称
        • Entry entry = SphU.entry(xxx) :假设资源名(配置的监控的资源名称)为xxx,获取当前资源的Entry对象;Entry对象是否存在就代表是否有限流或者降级
          • entry.exit():最终要释放上下文,避免线程之间出现上下文干扰
    • Entry:存储每个资源的信息,以及当前节点和来源节点等信息
      • 每一次资源调用都会创建一个 Entry。Entry 包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。
        • CtEntry 为普通的 Entry,在调用 SphU.entry(xxx) 的时候创建。特性:Linked entry within current context(内部维护着 parent 和 child)
          • CtEntry 构造函数中会做调用链的变换,即将当前 Entry 接到传入 Context 的调用链路上(setUpEntryFor)。
          • 资源调用结束时需要 entry.exit()。exit 操作会过一遍 slot chain exit,恢复调用栈,exit context 然后清空 entry 中的 context 防止重复调用。
    • Node:Sentinel 里面的各种种类的统计节点
      • StaticNode:统计节点,滑动窗口的结构
      • EntranceNode:入口节点,用于统计一个Context的总流量
      • DefaultNode:默认节点,用于统计一个资源的流量
      • ClusterNode:用于统计所有Context的同一资源的总流量

二、Sentinel核心源码

2.1、SentinelResourceAspect:Sentinel切面拦截类

  • 拦截@SentinelResource
    • 通过注解获取 value:获取对应的资源名,若不存在,则反射获取方法名作为资源名、entryType和resourceType
    • entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());//创建entry增强
    • 内部进入:entryWithType(name, resourceType, entryType, count, false, args)
      • count参数:代表当前请求的qps增长值,假设 是1,则代表,当前资源被访问流量+1
    • 然后进入重载方法,构建资源对象StringResourceWrapper,进入entryWithPriority方法
    • entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object… args)
      • prioritized:代表是否进行优先级处理,false:代表直接通过,true:代表需要判定
        • 等待时长,根据配置的流量规则进行计算
    • entryWithPriority方法详解
      • Context context = ContextUtil.getContext();//会从ThreaLocal中获取当前线程的上下文
      • context instanceof NullContext:如果是此上下文,则代表当前线程已经超过了上下文的阈值,则值初始化entry对象,不再进行任何规则检测
      • context == null:则创建一个默认的上下文
      • !Constants.ON:如果关闭了全局检测,则直接返回entry对象,不再进行任何规则检测
      • ProcessorSlot chain = lookProcessChain(resourceWrapper); //查找或创建slotChain,也就是获得一个完整的调用链路
        • 先从缓存中获取,key代表资源,value就是整个slotChain 调用链路
        • 如果没有找到或者超过了最大的链路规定数量,则chain == null,初始化Entry对象,不再进行任何规则检测
        • 否则创建一个新的链路并放到缓存中,并返回ProcessorSlot对象
      • 随后:chain.entry(context, resourceWrapper, null, count, prioritized, args); 对整个调用链路操作
        • 按照整个固定的调用链路,再次调用各slot的entry方法,然后进入StaticSlot的entry方法
      • StaticSlot的entry方法,就是流控规则的起始方法
      • fireEntry(context, resourceWrapper, node, count, prioritized, args);
        - 此方法就会调用后续的其他校验slot,验证流控规则方法(FlowSlot、DegradeSlot等流控降级等验证)
        • 会根据流控规则。抛出对应的错误异常
        • 如果没有被限流,则代表允许通过,会将通过线程数和通过请求数,分别增加传递过来的count(通常就是1)
    • FlowSlot(流控)分析:
      • checkFlow(resourceWrapper, context, node, count, prioritized); //检测流控规则方法
        • Collection rules = ruleProvider.apply(resource.getName());
          • 获取当前资源的所有设定的流控规则,循环执行 canPassCheck 方法,只要遇到无法通过的直接抛出异常
          • FLowRule对象就是配置的流控规则
            • 资源名、来源(limitApp)、阈值类型(grade)、单机阈值(count)
            • 流控模式(strategy)、关联资源(refResource)、流控效果(controlBehavior)
            • 预热时长(warmUpPeriodes)、排队等待超时时间(maxQueueingTimeMs)
            • 和管理端的配置一对一
    • DegradeSlot(降级)分析:
      • performChrck(resourceWrapper, context); //检测降级方法
        • List circuitBreakers = DegradeRuleManager.getCircuitBreakers(resourceWrapper.getName());
          • 获取当前资源的降级规则,循环执行tryPass方法,如果没有通过熔断规则,抛出异常
          • CircuitBreaker对象就是配置的降级规则
    • context创建:
      • 先从缓存的Map查找,key就是context,value就是Entry
        • 用到了双重检测,避免并发创建
      • 创建成功后,放到缓存和ThreaLocal
        • 用到了map迭代问题,为了避免其他地方迭代获取脏读数据,
          • 因此,给map赋值需要使用复制并交换的策略,也就是创建一个新的map增加属性,并将旧的map对象指向新的map
    • slotChain创建:
      • 先从缓存中找slotBuilder,如果有则直接返回,如果没有则创建一个默认的
        • slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);
        • 通过SPI创建一个规定顺序的slotChainBuilder
        • slotChainBuilder.build(); 构建一个SlotChain
      • slotChainBuilder.build()
        • List sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class);
        • slotChain.addLast(sortedSlot); //按照顺序依次添加到slotChain中
        • 通过SPI构建默认的slot链路

三、滑动时间算法

3.1、算法原理

  • 当某一个时间点接收到一个请求,会向前查找x时间的总请求量,判断和预定的请求阈值作比较
  • 问题:频繁的获取x时间的请求量,会对整体效率有所影响
    • 改进:将一整段时间窗口,再划分为多个样本窗口(样本窗口是某一段时间的数量统计)
      • 假设时间窗口100,划分为了四个样本窗口,每25就作为一个统计范围,后续的某个时间点。直接获取样本窗口的统计量,只分析处理样本窗口中的请求总量即可
        在这里插入图片描述

    3.2、相关源码解析

    • 滑动时间算法在StatisticSlot 中
      • entry方法中,执行node.addPassRequest(count)
        • 内部对滑动计数器进行数据统计 addPass方法
          • 获取当前时间所在的样本窗口:WindowWrap wrap = data.currentWindow();
            • 计算样本窗口的下标:int idx = calculateTimeIdx(timeMillis);
              • 样本窗口本质上是在一个数组中(LeadArray),每一个样本窗口,默认都是从0开始以此排序
            • 获取时间当前时间所在样本窗口的时间起始点:long windowStart = calculateWindowStart(timeMillis);
            • 因为设定的时间范围是固定的,所以整个过程是一个环形状,因此,当时间超过了设定的时间,会存在一种情况,当前时间计算的时间窗口回到了起始点的样本窗口,但是之前记录的样本窗口起始时间是之前的旧时间,因此在这里判断了样本窗口的起始时间,resetWindowTo(old, windowStart);方法对起始时间进行了更新
          • 将当前样本窗口的请求数+count:wrap.value().addPass(count);
        • 通过资源获取所有时间窗口样本中的请求量和总的时间窗口 相除。即可获取到当前通过的QPS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值