概述


1、Sentinel 概念

1.1、代码结构

sentinel架构 sentinel node_限流

1.2、核心概念

Resource

资源是 Sentinel 对所保护的内容的抽象,任何想保护的代码函数等都可以通过 SphU.entry 接口将其定义为一个资源,SphU.entry 接口的第一个参数描述了该资源的名称

Node

Node是sentinel中用来存储统计数据的内存结构,以树形结构和hash存储:

sentinel架构 sentinel node_分布式_02

一个resource按照不同入口设置不同统计结点,存储context级别的统计信息;一个resource下设置统一的cluster node,存储resource粒度的统计信息;每个cluster node根据来源下挂不同origin node,存储各个来源的统计信息。

配置中不同限流模式其实最终对应的就是选择不同的node进行计算:

  • 直接模式: 选择cluster node
  • 关联模式: 选择关联resource的cluster node
  • 应用涞源: 选择origin node
  • 链路模式: 选择default node

Node 类型:

  • StatisticNode:最为基础的统计节点,是后续三种节点的父类,包含秒级和分钟级两个滑动窗口结构。
  • DefaultNode:链路节点,用于统计调用链路上某个资源的数据,维持树状结构。
  • ClusterNode:簇点,用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据
  • EntranceNode:入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据。

sentinel架构 sentinel node_面试_03

Context

Context是对资源操作时的上下文环境, 同一个线程中通过thread local 进行传里, context中维护了currentEntry, 同一个线程中可以多次entry/exit, 所以entry同样维护了一个树状结构:

sentinel架构 sentinel node_Java_04

Entry

调用点,这里面保存着和某一特定资源的某一次调用相关的信息,比如这次调用的起始执行时间,结束时间,是否抛出异常,调用链路中的父子关系,该调用点对应资源的处理链(ProcessorSlotChain),还保存了该调用点所对应的资源统计节点

Slot

slot是规则处理器,通过责任链模式进行串联处理。

sentinel架构 sentinel node_分布式_05

1.3、核心类图

sentinel架构 sentinel node_sentinel架构_06

sentinel主要通过Node实现结点维护统计信息、通过LeapArray实现滑动窗口算法。

2、Sentinel 流程

2.1、规则配置

sentinel架构 sentinel node_sentinel架构_07

应用通过订阅监听模式实现各种监听器,监听然后对应不同配置的进行更新,图中列出了几个主要监听器,如: 规则配置、基本配置、集群配置等。

2.2、规则验证

sentinel架构 sentinel node_分布式_08

应用调用entry入口即启动了规则验证流程,主要步骤为获取context, 构建并执行slot chain,如果违反规则纪录失败计数,清理contxt;反之,则纪录成功计数,返回验证成功。

3、Sentinel 核心

3.1、限流算法

我们所熟知的限流算法包括: 1. 计数器 2. 固定窗口 3. 滑动窗口 4.漏桶 5.令牌桶等。

  • 1、计数器,维护一个计数器,访问进入计数器+1,如果超过阙值则拒绝、访问结束计数器-1。
  • 2、固定窗口,给定一个时间窗口,维护一个计数器、如果窗口内访问次数大于阙值则拒绝。
  • 3、滑动窗口,固定窗口有临界问题,所以提出滑动窗口思想,将一个大的时间窗口切分成更细粒度的子窗口,每个子窗口单独统计,每过一个子窗口的时间,就向右滑动一个子窗口。
  • 4、漏桶,滑动窗口不能解决平滑度问题,为了解决平滑度问题,提出漏桶算法进行流速控制。
  • 5、令牌桶: 跟漏桶算法相反,以很定的速度产生令牌,桶满则丢弃,请求访问时从桶里后去令牌,如果能够获取,则通过否则拒绝。

Sentinel中主要使用了计数器滑动窗口漏桶限流思想。比如对于线程数的流控,就是直接使用计数器进行限制,对于QPS的限制则使用了滑动窗口的思想,滑动窗口也是Sentinel的主要流控实现方式。

3.2、滑动窗口

sentinel架构 sentinel node_限流_09

Sentinel支持秒级的窗口,默认窗口大小1秒,假设分为5个子窗口,则每个子窗口的大小为200ms, 如上图,每个格子有个索引timeId, 那窗口是如何进行滑动呢?算法的关键实现就是在前面类图提到过的currentWindow()方法:

  • 1、根据当前时间戳计算timeId
  • 2、根据timdId获取当前子窗口
  • 3、如果子窗口不存在,则创建子窗口
  • 4、如果子窗口已经存在,则根据当前时间计算开始开始时间,对比子窗口的开始时间,如果开始时间一致,则说明此子窗口即当前窗口,如果大于子窗口时间,则需要滑动
  • 5、滑动其实就是resetWidow, 包括reset windowStartTime, window中的统计信息等,如上图所示

3.3、优先级

sentinel架构 sentinel node_面试_10

优先级的目的是为了区分正常流量和其他低优先级流量,比如压测流量。对于正常流量,如果超过阙值,不想直接拒绝,尝试借用将来窗口的指标,如果借用成功,则请求通过,如果借用失败,则拒绝。当时间窗口真正走到将来的时间点时,需要将借用窗口的计数器恢复设置。

那如何当前请求是否可以借用呢?如下图,主要维护一个借用时间阙值,计算一秒窗口内的总数有没有超过QPS,如果没有超过看借用时间是否超过借用阙值:

sentinel架构 sentinel node_限流_11

3.4、热点参数

一般资源的定义需要可枚举的,不能爆炸式的,假如对每个userId进行限流,这样会导致sentinel的存储空间爆炸式增长,最后导致OOM. 对于这种细粒度的参数限流,sentinel提供了热点参数方案。

如图:

热点参数就是在正常统计信息而外增加了参数统计,一样支持线程数和QPS两种限流方式,对于线程数,每个参数索引维护了一个LRU cache; 对于QPS,每个参数索引、每个子窗口、每个统计纬度维护了一个LRU cache, cache中维护了各种计数。通过LRU cache可以避免内存爆炸, 达到热点参数限流的目的。

sentinel架构 sentinel node_限流_12