1、什么是Sentinel?
Sentinel 是分布式系统的防御系统。以流量为切入点,通过动态设置的流量控制、服务熔断降级、系统负载保护等多个维度保护服务的稳定性,通过服务降级增强服务被拒后用户的体验。
官网:https://github.com/alibaba/Sentinel/wiki/
2、Sentinel特性
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
- 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
3、Sentinel 模块
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo/Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
4、Sentinel基本概念
Sentinel 实现限流、隔离、降级、熔断功能,本质要做的就是两件事:
- 统计数据:统计某个资源的访问数据,例如:QPS、RT 等信息;
- 规则判断:判断限流规则、隔离规则、降级规则、熔断规则是否满足。
资源
- 资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。
- 只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来表示资源。
- 资源就是希望被 Sentinel 保护的业务,例如项目中定义的 Controller 方法,就是默认被 Sentinel 保护的资源
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
5、Sentinel大致工作原理/工作流程
6、Sentinel核心组件
1、ProcessorSlotChain
Sentinel 的核心骨架是 ProcessorSlotChain,这个类基于责任链模式来设计,将不同的功能(限流、降级、系统保护等)封装为一个一个的 Slot,请求进入后逐个执行即可。系统会为每个资源创建一套 SlotChain。SlotChain 其实可以分为两部分:统计数据构建部分(statistic)和判断部分(rule checking)
2、责任链中的 SlotChain 分为两大类
统计数据构建部分(statistic)
- NodeSelectorSlot:
负责构建簇点链路中的节点(DefaultNode),将这些节点形成链路树
- ClusterBuilderSlot:
负责构建某个资源的 ClusterNode,ClusterNode 可以保存资源的运行信息以及来源信息(origin 名称),例如响应时间、QPS、block 数量、线程数、异常数等
- StatisticSlot:
负责统计实时调用数据,包括运行信息、来源信息等
规则判断部分(rule checking)
- AuthoritySlot:负责授权规则(来源控制)(黑白名单)
- LogSlot:负责日志打印
- SystemSlot:负责系统保护规则(系统qps,thread等等防护)
- ParamFlowSlot:
负责热点参数限流规则
- FlowSlot:
负责普通限流规则
- GatewayFlowSlot:
负责网关限流规则
- DegradeSlot:
负责降级规则
3、slotChain调用顺序
4、Context
Context 是对资源操作的上下文,每个资源操作必须属于一个 Context。如果代码中没有指定 Context,则会创建一个 name 为 sentinel_default_context 的默认 Context
。一个 Context 生命周期中可以包含多个资源操作。Context 生命周期中最后一个资源在 exit() 时会清理该 Context,也就意味着这个 Context 生命周期结束了
。
说白了context指的是 调用方访问受保护的资源时,调用链路的上下文
- Context 代表调用链路上下文,贯穿一次调用链路中的所有资源(Entry)。Context 维持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息;
- Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter 的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, f) 来变换 context;
后续的 Slot 都可以通过 Context 拿到 DefaultNode 或者 ClusterNode,从而获取统计数据,完成规则判断;
- Context 初始化的过程中,
会创建 EntranceNode ,entranceNode Name往往指的是调用方,资源,default。entranceNode 作为流量入口,作为一个统计维度
public void contextDemo() {
// 创建一个来自于 appA 访问的 Context,”entranceOne“ 为 Context 的名称,”appA“ 为来源名称
ContextUtil.enter("entranceOne", "appA");
// Entry 就是一个资源操作对象
Entry resource1 = null;
Entry resource2 = null;
try {
// 获取资源 resource1 的 entry
resource1 = SphU.entry("resource1");
// 代码至此,说明当前对资源 resource1 的请求通过了流控
// 对资源 resource1 的相关业务处理。。。
// 获取资源 resource2 的 entry
resource2 = SphU.entry("resource2");
// 代码至此,说明当前对资源 resource2 的请求通过了流控
// 对资源 resource2 的相关业务处理。。。
} catch (BlockException e) {
// 代码至此,说明请求被限流,这里执行降级处理
} finally {
if (resource1 != null) {
resource1.exit();
}
if (resource2 != null) {
resource2.exit();
}
}
// 释放Context
ContextUtil.exit();
// --------------------------------------------------------
// 创建另一个来自于 appA 访问的 Context,
// entranceTwo 为 Context 的 name
ContextUtil.enter("entranceTwo", "appA");
// Entry 就是一个资源操作对象
Entry resource3 = null;
try {
// 获取资源 resource2 的 entry
resource2 = SphU.entry("resource2");
// 代码至此,说明当前对资源 resource2 的请求通过了流控
// 对资源 resource2 的相关业务处理。。。
// 获取资源 resource3 的 entry
resource3 = SphU.entry("resource3");
// 代码至此,说明当前对资源 resource3 的请求通过了流控
// 对资源 resource3 的相关业务处理。。。
} catch (BlockException e) {
// 代码至此,说明请求被限流,这里执行降级处理
} finally {
if (resource2 != null) {
resource2.exit();
}
if (resource3 != null) {
resource3.exit();
}
}
// 释放Context
ContextUtil.exit();
}
5、Entry
entry为令牌,当访问受限的资源被允许时,会获取到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 会清空 entry 中的 context 防止重复调用
。
6、Node
簇点链路:就是项目内的调用链路,链路中被监控的每个接口就是一个资源。默认情况下 sentinel 会监控 SpringMVC 的每一个端点(Endpoint),因为 SpringMVC 的每一个端点就是调用链路中的一个资源。流控、熔断等都是针对簇点链路中的资源来设置的。
注意哦!
簇点链路: 通俗的讲是上下文Context中的逻辑链路,context内部包含着从entranceNode出发的到受保护资源的至少一条链路,不同使用方式,链路树可能会不一样哦,entranceNode往往作为调用方!
Sentinel 里面的各种种类的统计节点,即簇点链路是由一个个 Node 组成的:
所有的节点都可以记录对资源的访问统计数据,因为都是 StatisticNode 的子类,StatisticNode 是最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构
。
/**
* 定义了一个使用数组保存数据的计量器(以"秒"为单位)
* SAMPLE_COUNT:样本窗口数量,默认值为 2
* INTERVAL:时间窗长度,默认值为 1000 毫秒
*/
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
IntervalProperty.INTERVAL);
/**
* 定义了一个使用数组保存数据的计量器(以"分"为单位)
*/
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);
节点 Node 按照作用分为以下几类:
- DefaultNode:链路节点,用于统计调用链路上某个资源的数据,维持树状结构,即代表链路树中的每一个资源。一个资源出现在不同链路中时,会创建不同的 DefaultNode 节点
- ClusterNode:簇点,代表资源,一个资源不管出现在多少链路中,只会有一个 ClusterNode,用于记录当前资源被访问的所有统计数据之和。即用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据(类型为 StatisticNode)。特别是 Constants.ENTRY_NODE 节点用于统计全局的入口资源数据
- EntranceNode:入口节点,特殊的链路节点(DefaultNode),对应某个 Context 入口的所有调用数据。Constants.ROOT 节点也是入口节点。
例如:
现在两个业务接口:
业务 1:Controller 中的资源 /order/query 访问了 Service 中的资源 /cat
业务 2:Controller 中的资源 /order/save 访问了 Service 中的资源 /cat
创建的链路图如下:
Node 节点总结:
- Node:用于完成数据统计的接口;
- StatisticNode:统计节点,是 Node 接口的实现类,用于完成数据统计;
- EntranceNode:入口节点,一个 Context 会有一个入口节点,用于统计当前 Context 的总体流量数据;
- DefaultNode:默认节点,用于统计一个资源在当前 Context 中的流量数据;
- ClusterNode:集群节点,用于统计一个资源在所有 Context 中的总体流量数据;
7、Slot插槽
1、NodeSelectorSlot 用户调用链路构建
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。
ContextUtil.enter("entrance1", "appA");
Entry nodeA = SphU.entry("nodeA");
if (nodeA != null) {
nodeA.exit();
}
ContextUtil.exit();
上述代码通过 ContextUtil.enter() 创建了一个名为 entrance1 的上下文,同时指定调用发起者为 appA;接着通过 SphU.entry()请求一个 token,如果该方法顺利执行没有抛 BlockException,表明 token 请求成功。
根据调用者的不同(entranceNode)(context)不同,同一资源存在者,多种调用链路
ContextUtil.enter("entrance1", "appA");
Entry nodeA = SphU.entry("nodeA");
if (nodeA != null) {
nodeA.exit();
}
ContextUtil.exit();
ContextUtil.enter("entrance2", "appA");
nodeA = SphU.entry("nodeA");
if (nodeA != null) {
nodeA.exit();
}
ContextUtil.exit();
在内存中生成以下结构
machine-root
/ \
/ \
EntranceNode1 EntranceNode2
/ \
/ \
DefaultNode(nodeA) DefaultNode(nodeA)
2、ClusterBuilderSlot ,统计簇点构建(资源的所有链路数据统计)
用于存储资源的统计信息以及调用者信息,例如该资源的 RT,QPS,thread count,Block count,Exception count 等等,这些信息将用作为多维度限流,降级的依据。简单来说,就是用于构建 ClusterNode。
3、StatisticSlot 数据统计
StatisticSlot 是 Sentinel 最为重要的类之一,用于根据规则判断结果进行相应的统计操作。用于记录、统计不同纬度的 runtime 指标监控信息。
- 执行 entry() 方法的时候:依次执行后面的判断 slot。每个 slot 触发流控的话会抛出异常(BlockException 的子类)。若有 BlockException 抛出,则记录 block 数据;若无异常抛出则算作可通过(pass),记录 pass 数据。
- 执行 exit() 方法的时候:若无 error(无论是业务异常还是流控异常),记录 complete(success)以及 RT,线程数-1。
- 记录数据的维度:线程数+1、记录当前 DefaultNode 数据、记录对应的 originNode 数据(若存在 origin)、累计 IN 统计数据(若流量类型为 IN)。
StatisticSlot 是 Sentinel 的核心功能插槽之一,用于统计实时的调用数据。
- clusterNode:资源唯一标识的 ClusterNode 的实时统计
- origin:根据来自不同调用者的统计信息
- defaultNode: 根据入口上下文区分的资源 ID 的 runtime 统计
- 入口流量的统计
4、ParamFlowSlot
热点参数限流,对应热点流控。
热点参数限流是分别统计参数值相同的请求,判断是否超过 QPS 阈值。
5、FlowSlot
用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制。对应流控规则。
这个 slot 主要根据预设的资源的统计信息,按照固定的次序,依次生效。如果一个资源对应两条或者多条流控规则,则会根据如下次序依次检验,直到全部通过或者有一个规则生效为止:
三种流控模式:
- 直接模式:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
- 关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
- 链路模式:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
三种流控模式,从底层数据统计角度来看,分为两类
- 对进入资源的所有请求(ClusterNode)做限流统计:直接模式、关联模式
- 对进入资源的部分链路(DefaultNode)做限流统计:链路模式
三种流控效果:
- 快速失败:达到阈值后,新的请求会被立即拒绝并抛出 FlowException 异常。是默认的处理方式,基于滑动时间窗口算法。
- warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。基于滑动时间窗口算法,只不过阈值是动态的
- 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长,基于漏桶算法。
滑动时间窗的功能分两部分:
6、GatewayFlowSlot 同上
7、AuthoritySlot
来源访问控制
根据配置的黑白名单和调用来源信息,来做黑白名单控制。对应授权规则 。
白名单:来源(origin)在白名单内的调用者允许访问
黑名单:俩与(origin)在黑名单内的调用者不允许访问
8、DegradeSlot
熔断降级
通过统计信息及预设的规则,来做熔断,对应降级规则。
熔断降级是解决雪崩问题的重要手段,其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求,而当服务恢复时,断路器会放行访问该服务的请求
器熔断策略有三种:慢调用、异常比例、异常数
- 慢调用:业务的响应时长(RT)大于指定时长的请求被认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。例如下图配置,RT 超过 500ms 的调用是慢调用,统计最近 10000ms 内的请求,如果请求量超过 10 次,并且慢调用比例不低于 0.5,则触发熔断,熔断时长为 5s,然后进入 half-open 状态,放行一次请求做测试
- 异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。例如下图配置,统计最近 1000ms 内的请求,如果请求超过 10 次,并且异常比例不低于 0.4,则触发熔断,熔断时长为 5s,然后进入 half-open 状态,放行一次请求做测试
9、SystemSlot
系统保护
通过系统的状态,例如 load1 等,来控制总的入口流量。对应系统规则。
这个 slot 会根据对于当前系统的整体情况,对入口资源的调用进行动态调配。其原理是让入口的流量和当前系统的预计容量达到一个动态平衡。
注意系统规则只对入口流量起作用(调用类型为 EntryType.IN),对出口流量无效。可通过 SphU.entry(res, entryType) 指定调用类型,如果不指定,默认是 EntryType.OUT