0%

sentinel研究手册

Sentinel 研究手册

为什么需要流量控制?

首先明确一个概念:

服务雪崩:由于某个微小的服务挂了,导致整一大片的服务都不可用。

举个例子:

在一个微服务架构模式下的系统当中,会存在很多个服务,这些服务彼此之间或多或少会存在调用关系。假设服务A1调用了服务B1,服务B又调用了服务C1。在服务C1由于某种原因,出现了阻塞,然后服务B1、A1都在等待C1的响应结果。此时如果在没有感知到这三个节点的状态的情况下,又恰好有许多请求转发到了C1上,使得C1出现了过载现象

雪崩发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问题,不会影响到其它服务的正常运行。因此,我们需要引入一种机制,对服务质量进行监控。如果哪个节点中出现了服务不可用或者是响应缓慢时,我们就减少对其调用的频率,将其工作任务转发给当前节点所在服务集群的其他节点进行处理。过一段时间后再逐渐恢复对其的调用。

服务雪崩的常见解决方案

要防止雪崩的扩散,我们就要做好服务的容错。常见的容错思路有隔离、超时、限流、熔断、降级这几种。

隔离机制

在上游服务调用多个下游服务的时候,给不同的下游服务分配固定的线程——不会把所有的线程都分配给某一个服务。

例如:服务A内总共有100个线程,现在服务A可能会调用服务B、服务C、服务D。隔离策略:调用服务B分配30个线程;调用服务C分配30个线程;调用服务D分配40个线程。这样进行资源的隔离,保证即使下游某个服务挂了,也不至于把服务A的线程消耗完。

超时机制

在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断开请求,释放掉线程。

限流机制

限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。

熔断机制

在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

服务熔断一般有三种状态:

  1. 熔断关闭状态(Closed):服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
  2. 熔断开启状态(Open):后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法。
  3. 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。

Sentinel的介绍

Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

Sentinel与其他类似组件比较

Sentinel Hystrix Resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(Leaparray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
限流 基于QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 简单的监控查看 不提供控制台,,可对接其他监控系统

Sentinel的相关概念

资源(Resource)

在Sentinel参与到的系统当中,一切皆可视为资源。

资源可以是一段代码,又或者是一个接口,Sentinel中并没有什么强制规定,但是实际项目中一般以一个接口为一个资源,比如说一个http接口,又或者是rpc接口,它们就是资源,可以被保护。

资源是通过Sentinel的API定义的,每个资源都有一个对应的名称,比如对于一个http接口资源来说,Sentinel默认的资源名称就是请求路径。

Sentinel可以视为保护资源的卫兵。

说明:

  1. “资源”在一个系统中是唯一的吗?即不同接口上的资源名称可以重复吗?
    答:可以重复。resource相当于是一个分类,可以加在不同的接口上。
  2. 一个资源名称,在两个接口上一起使用时,限流统计规则是两个接口独立计算。
  3. 资源定义的方式有很多种,建议通过注解。

规则(Rules)

规则也是一个重要的概念,规则其实比较好理解,比如说要对一个资源进行限流,那么限流的条件就是规则,后面在限流的时候会基于这个规则来判定是否需要限流。

Sentinel的规则分为流量控制规则、熔断降级规则以及系统保护规则,不同的规则实现的效果不一样。

流量控制

这里的流量控制指的是对各个服务节点网络访问请求的控制,根据服务节点的处理能力,动态地调整访问流量阈值。

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

arch

服务熔断、服务降级和服务限流

熔断降级是防止出现服务雪崩而采取的一种措施。当一个节点堆积了许多未来的及处理的请求时,就会发生频繁超时、异常比例升高。因此需要感知服务调用状态,当出现这中现象时,再有服务请求要访问这个节点,就阻止访问,直接返回调用失败的提示,释放资源。然后再隔一段时间后又慢慢地尝试恢复对其访问,测试其有没有恢复,如果恢复,则恢复对其的正常访问。这便是服务熔断。它在服务调用方直接阻止了对服务的调用。

服务降级则是相对于被访问节点而言。当这个节点面对大量的服务请求处理的力不从心是,果断放弃当前节点中的边缘业务,留下资源,用于处理核心业务。当发生对边缘业务的处理请求时,不做真正的业务处理,直接返回一个友好的提示,释放资源,从而保证对核心业务的处理能力。

服务限流则对访问当前节点的请求数直接做出限定,比如规定1秒内,只能有100请求访问当前节点,则第101个服务请求在这一秒内来访问的话,会被直接给拒绝掉。

Sentinel在当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时,对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

服务熔断作用于服务调用方(服务上游),服务降级作用于服务提供方(服务下游)

Sentinel如何实现熔断降级

限制资源并发线程的数量

优点:没有线程切换的损耗,也不需要预先分配线程池的大小。

说明:当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

通过响应时间对资源进行降级

说明:当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

Sentinel核心类

Entry

每一次资源调用都会创建一个 Entry

Entry中持有本次对资源调用的相关信息:

  • createTime:创建该Entry的时间戳。
  • ProcessorSlot链
  • curNode:Entry当前是在哪个节点。
  • orginNode:Entry的调用源节点。
  • resourceWrapper:Entry关联的资源信息。
    • 任何一个被保护的资源都被封装成resourceWrapper对象

Entry是一个抽象类,CtEntryEntry的实现,CtEntry 持有Context和调用链的信息。

1
2
//代表要处理名称为HelloWorld的资源。操作成功后会返回一个Entry对象,否则抛出异常代表不处理当前请求(可以认为是规则限制)
Entry entry = SphU.entry("HelloWorld")

Sphu.entry

SphU.entry()通过一系列的调用最终调用到CtSph的entryWithPriority()方法上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//resourceWrapper:是StringResourceWrapper对象,表示资源
//count:表示令牌数,默认是1,一般一个请求对应一个令牌,也可以指定一个请求对应多个令牌,如果令牌不够,则禁止访问
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
//构建上下文对象,上下文对象存储在ThreadLocal中
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
return new CtEntry(resourceWrapper, null, context);
}
//一般的线程第一次访问资源,context都是null,我们也可以在应用程序中使用ContextUtil自己创建Context对象
if (context == null) {
//下面创建了一个名字为sentinel_default_context的Context对象
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
//全局开关,可以使用它来关闭sentinel
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}
//使用SPI构建slot链,每个slot对象都有一个next属性,可以使用该属性指定下一个slot对象
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
//创建Entry对象
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
//对该请求,遍历每个slot对象
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}

说明:创建完Context对象后,使用SPI构建slot链,之后是创建Entry对象,之后就是遍历slot链以决定是否允许该请求访问资源。

需要注意的一点:CtEntry 构造函数中会做调用链的变换,即将当前 Entry 接到传入 Context 的调用链路上(setUpEntryFor)。

资源调用结束时需要 entry.exit()。exit 操作会过一遍 slot chain exit,恢复调用栈,exit context 然后清空 entry 中的 context 防止重复调用。

Context

Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。Context 维持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息。Context保存在ThreadLocal中。

Context可以在资源调用之前手动通过ContextUtil.enter(name,origin)创建,name为当前context的名称,origin为调用方名称,当配置了调用方限流的时候会用到。

1
2
3
//name表示Context的名称或者链路入口的名称,origin表示调用来源的名称,默认为空字符串
public static Context enter(String name, String origin);
public static Context enter(String name);

初始化

在初始化slot责任链部分前,执行context的初始化。在Context初始化的过程中,会把EntranceNode加入到Root子节点中(实际Root本身是一个特殊的EntranceNode),并把EntranceNode放到contextNameNodeMap中。

1
2
3
4
5
6
7
8
9
10
               root
/ \
/ \
entranceNode1 entranceNode2 -------表示入口节点对象EntranceNode
/ \
/ \
DefaultNode(nodeA) DefaultNode(nodeB) ---------内部创建DefaultNode节点
| |
| |
ClusterNode(nodeA) ClusterNode(nodeB) ------------记录资源的访问数据

每调用一次SphU.entry()方法都会在访问链路树上增加一个子节点,通过这个树可以还原出资源的访问路径。
每访问一个资源,Context对象都使用curEntry属性记录下正在访问资源对应的Entry对象,Entry对象有一个parent属性记录下父Entry,比如上面代码中,nodeB的父Entry是nodeA,Entry还有一个curNode属性,该属性记录了对应的DefaultNode对象。每个DefaultNode对象还有一个ClusterNode类的属性clusterNode,clusterNode的作用是记录被访问的资源的统计数据,比如平均响应时间、总请求数、QPS等,FlowSlot便是依据这些数据来判断是否允许访问资源。Context可以通过上述这些属性构建出一个完整的资源访问树,并将资源访问数据更新到对应的ClusterNode对象中。

维持方式

通过 ThreadLocal 传递,只有在入口 enter 的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, f) 来变换 context。

Node

Node中保存了对资源的实时数据的统计,Sentinel中的限流或者降级等功能就是通过Node中的数据进行判断的。Node是一个接口,里面定义了各种操作request、exception、rt、qps、thread的方法。

Sentinel 里面的各种种类的统计节点:

  • StatisticNode:最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构。用于完成数据统计。

  • DefaultNode:默认节点,用于统计一个资源在当前Context中的流量数据。

  • ClusterNode:集群节点,用于统计一个资源在所有Context中的总体流量数据。

  • EntranceNode:入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据,创建维度为resource。Constants.ROOT 节点也是入口节点。

构建的时机:

  • EntranceNodeContextUtil.enter(xxx) 的时候就创建了,然后塞到 Context 里面。
  • NodeSelectorSlot:根据 context 创建 DefaultNode,然后 set curNode to context。
  • ClusterBuilderSlot:首先根据 resourceName 创建 ClusterNode,并且 set clusterNode to defaultNode;然后再根据 origin 创建来源节点(类型为 StatisticNode),并且 set originNode to curEntry。

几种 Node 的维度(数目):

  • ClusterNode 的维度是 resource
  • DefaultNode 的维度是 resource * context,存在每个 NodeSelectorSlot 的 map 里面
  • EntranceNode 的维度是 context,存在 ContextUtil 类的 contextNameNodeMap 里面
  • 来源节点(类型为 StatisticNode)的维度是 resource * origin,存在每个 ClusterNode 的 originCountMap 里面

Slot Chain

sentinel在内部创建了一个责任链,责任链是由一系列Processor Slot对象组成的,每个Processor Slot对象负责不同的功能,外部请求是否允许访问资源,需要通过责任链的校验,只有校验通过的,才可以访问资源,如果被校验失败,会抛出BlockException异常。

辅助资源指标数据统计的Process Slot

  • NodeSelectorSlot 负责收集资源路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级、数据统计。
  • ClusterBuilderSlot建ClusterNode对象,该对象用于统计访问资源的QPS、线程数、异常、响应时间等,每个资源对应一个ClusterNode对象。
  • StatisticSlot 用于从多个维度(入口流量、调用者、当前被访问资源)统计响应时间、并发线程数、处理失败个数、处理成功个数等。核心的Slot
  • FlowSlot 用于流控,可以根据QPS或者每秒并发线程数控制,当QPS或者并发线程数超过设定值,便会抛出FlowException异常。FlowSlot依赖于StatisticSlot的统计数据。

这些辅助ProcessorSlot需要严格的顺序执行

NodeSelectorSlot ——> ClusterBuilderSlot ——> StatisticSlot

实现降级功能的Process Slot

  • AuthoritySlot 黑白名单校验,按照字符串匹配,如果在黑名单,则禁止访问。
  • DegradeSlot 用于服务降级,如果发现服务超时次数或者报错次数超过限制,DegradeSlot将禁止再次访问服务,等待一段时间后,DegradeSlot试探性的放过一个请求,然后根据该请求的处理情况,决定是否再次降级。
  • SystemSlot 校验QPS、并发线程数、系统负载、CPU使用率、平均响应时间是否超过限制,使用滑动窗口算法统计上述这些数据。
  • LogSlot 打印日志。

总体的框架如下:

arch overview

详细说明:

img

自定义slot

我们也可以添加自定义的slot,只需要实现ProcessorSlot接口,在com.alibaba.csp.sentinel.slotchain.ProcessorSlot文件中添加自定义类的全限定名,然后使用注解@SpiOrder指定顺序即可。

Slot Chain SPI

StatisticSlot

StatisticSlot 是 Sentinel 最为重要的类之一,用于根据规则判断结果进行相应的统计操作。

entry 的时候:依次执行后面的判断 slot。每个 slot 触发流控的话会抛出异常(BlockException 的子类)。若有 BlockException 抛出,则记录 block 数据;若无异常抛出则算作可通过(pass),记录 pass 数据。

exit 的时候:若无 error(无论是业务异常还是流控异常),记录 complete(success)以及 RT,线程数-1。

记录数据的维度:线程数+1、记录当前 DefaultNode 数据、记录对应的 originNode 数据(若存在 origin)、累计 IN 统计数据(若流量类型为 IN)。

Process Slot Chain

Sentinel 的核心骨架,将不同的 Slot 按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级、系统保护)组合在一起。

Sentinel 会为每个资源创建且仅创建一个 ProcessorSlotChain,只要名称相同就认为是同一个资源。ProcessorSlotChain 被缓存在 CtSph.chainMap 静态字段,key 为资源 ID.

SPI 扩展

Sentinel 提供多样化的 SPI 接口用于提供扩展的能力。开发者可以在用同一个 sentinel-core 的基础上自行扩展接口实现,从而可以方便地根据业务需求给 Sentinel 添加自定义的逻辑。目前 Sentinel 提供如下的扩展点:

  • 初始化过程扩展:提供 InitFunc SPI接口,可以添加自定义的一些初始化逻辑,如动态规则源注册等。
  • Slot/Slot Chain 扩展:用于给 Sentinel 功能链添加自定义的功能并自由编排。
  • 指标统计扩展(StatisticSlot Callback):用于扩展 StatisticSlot 指标统计相关的逻辑。
  • Transport 扩展:提供 CommandHandler、CommandCenter 等接口,用于对心跳发送、监控 API Server 进行扩展。
  • 集群流控扩展:可以方便地定制 token client/server 自定义实现,可参考对应文档
  • 日志扩展:用于自定义 record log Logger,可用于对接 slf4j 等标准日志实现。

image

Sentinel部署k8s

sentinel docker镜像化

windows系统下载一个Docker Desktop。

  1. 下载sentinel-dashboard.jar
    https://github.com/alibaba/Sentinel/releases

    最新版本为:1.8.6

  2. 编写Dockerfile

    1
    2
    3
    4
    5
    6
    7
    8
    FROM openjdk:8-jre-slim

    COPY sentinel-dashboard-1.8.6.jar sentinel-dashboard.jar

    ENV JAVA_OPTS="-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard"

    ENTRYPOINT java ${JAVA_OPTS} -jar sentinel-dashboard.jar

    此处需要注意的是:版本号一致;1.8.6版本的默认端口号为8080

  3. 将jar包和Dockerfile放到同一目录,执行打包命令

1
docker build -t sentinel/sentinel-dashboard:1.8.6 -f Dockerfile .

说明:

1
2
3
4
5
6
docker build  -t ImageName:TagName dir
**选项**
- `-t` − 给镜像加一个Tag
- `ImageName` − 给镜像起的名称
- `TagName` − 给镜像的Tag名
- `Dir` − Dockerfile所在目录
  1. 给镜像打个标签

    1
    docker tag sentinel-dashboard:1.8.6 10.99.180.131/jxcccommon/jxcc-sentinel:1.0

    说明:标签类似于版本

  2. 本地测试镜像是否正常运行

    1
    docker run --name sentinel -p 8080:8080 -td 10.99.180.131/jxcccommon/jxcc-sentinel:1.0

    访问localhost:8080

  3. 登录10.99.180.131 harbor.jxcc.com,需要输入用户名密码

    此处可以上传到docker-hub仓库。

  4. 上传镜像到harbor仓库

    1
    docker push 10.99.180.131/jxcccommon/jxcc-sentinel:1.0

k8s容器化部署

  1. 创建工作负载;确定好pull的镜像地址和内部容器端口号
  2. 创建服务;选择1中创建的工作负载
  3. 启动服务

方案选择

方案一:微服务单独集成sentinel

将 Sentinel 集成到单个微服务的优缺点:

优点:

  1. 隔离性: 在每个微服务中集成 Sentinel 可以实现更细粒度的流量控制和熔断策略,使得每个微服务可以根据自身特点和需求进行配置。
  2. 更灵活的限流策略: 每个微服务可以根据自己的业务场景定制限流策略,避免不同微服务之间的相互影响。
  3. 独立运维: 每个微服务独立配置 Sentinel,减少了对网关的依赖,即使网关出现问题也不会影响到其他微服务。

缺点:

  1. 配置重复: 如果有多个微服务,可能需要在每个微服务中都配置 Sentinel 规则,导致配置重复和维护成本增加。
  2. 监控分散: 每个微服务的监控数据被分散到各个微服务中,可能需要额外的工作来汇总和分析监控数据。

方案二:gateway集成sentinel

相较于单独使用sentinel,其优点在于:

  1. 统一的监控:Gateway集成Sentinel后,可以将所有微服务的请求都通过Gateway进行转发,这样可以更方便地对所有请求进行统一的监控和管理,从而实现更全面、更深入的监控。

  2. 更高的可靠性:Gateway集成Sentinel后,可以在网关层面对请求进行限流和熔断,从而保证微服务的可靠性和稳定性。而如果单独使用Sentinel,可能需要在每个微服务中都进行限流和熔断的配置,这样会增加配置的复杂度和维护的难度。

  3. 更好的灵活性:Gateway集成Sentinel后,可以根据不同的业务场景和需求,灵活地配置限流和熔断规则,从而更好地满足不同的业务需求。而如果单独使用Sentinel,可能需要在每个微服务中都进行限流和熔断的配置,这样会限制配置的灵活性。

  4. 更好的性能:Gateway集成Sentinel后,可以在网关层面对请求进行限流和熔断,从而减少了微服务的请求压力,提高了系统的性能和响应速度。而如果单独使用Sentinel,可能需要在每个微服务中都进行限流和熔断的配置,这样会增加系统的负担,降低系统的性能。

缺点:

  1. 单点故障: 如果网关本身出现故障,整个微服务架构的流量控制和熔断功能可能会受到影响。
  2. 性能瓶颈: 将流量控制和监控逻辑集中在网关中可能增加网关的负载,需要进行适当的性能测试和优化。

访问地址:10.99.186.32:31783

控制台和客户端通信原理

控制台的使用

控制台使用懒加载,在第一次访问的时候才会开始进行初始化,并向控制台发送心跳和客户端规则等信息。

交互方式

  1. 首先 sentinel-core 向 dashboard 发送心跳包
  2. dashboard 将 sentinel-core 的机器信息保存在内存中
  3. dashboard 根据 sentinel-core 的机器信息通过 httpClient 获取实时的数据
  4. sentinel-core 接收到请求之后,会找到具体的 CommandHandler 来处理
  5. sentinel-core 将处理好的结果返回给 dashboard

连接 dashboard

sentinel-core 在初始化的时候,通过 JVM 参数中指定的 dashboard 的 ip 和 port,会主动向 dashboard 发起连接的请求,该请求是通过 HeartbeatSender 接口以心跳的方式发送的,并将自己的 ip 和 port 告知 dashboard。这里 sentinel-core 上报给 dashboard 的端口是 sentinel 对外暴露的自己的 CommandCenter 的端口。

HeartbeatSender 有两个实现类,一个是通过 http,另一个是通过 netty。

该类通过一个 HttpClient 向 dashboard 发送了自己的信息,包括 ip port 和版本号等信息。

其中 consoleHost 和 consolePort 的值就是从 JVM 参数 csp.sentinel.dashboard.server 中获取的。

dashboard 在接收到 sentinel-core 的连接之后,就会与 sentinel-core 建立连接,并将 sentinel-core 上报的 ip 和 port 的信息包装成一个 MachineInfo 对象,然后通过 SimpleMachineDiscovery 将该对象保存在一个 map 中,如下图所示:

在这里插入图片描述

请求数据

当 dashboard 有了具体的 sentinel-core 实例的 ip 和 port 之后,就可以去请求所需要的数据了。

当在页面上查询某一台机器的限流的规则时,是将该机器的 ip 和 port 以及 appName 都传给了服务端,服务端通过这些信息去具体的远程实例中请求所需的数据,拿到数据后再封装成 dashboard 所需的格式返回给前端页面进行展示。

客户端

sentinel-core 在启动的时候,执行了一个 InitExecutor.init 的方法,该方法会触发所有 InitFunc 实现类的 init 方法。

因为这里我们引入了sentinel-transport-simple-http模块,所以使用spi加载InitFunc的子类的时候会新加载两个子类实例,分别是:CommandCenterInitFunc、HeartbeatSenderInitFunc。然后会遍历loader,根据@InitOrder的大小进行排序,并封装成OrderWrapper放入到initList中。

CommandCenterInitFunc 则会启动一个 CommandCenter 对外提供 sentinel-core 的数据服务,而这些数据服务是通过一个一个的 CommandHandler 来提供的

Sentinel 控制台、Nacos 和客户端应用程序之间的交互过程

  1. 配置规则: 在 Sentinel 控制台中配置流控、降级等规则,输入资源名、限流参数、策略等信息,并保存配置。
  2. 推送配置到 Nacos: Sentinel 控制台会将配置的规则信息推送到 Nacos 配置中心。这需要在 Nacos 上创建相应的配置项。
  3. 客户端应用程序从 Nacos 拉取配置: 客户端应用程序通过 Nacos 的配置监听机制,定时或实时地从 Nacos 中拉取最新的规则配置信息。
  4. 解析规则: 客户端应用程序将从 Nacos 拉取的规则配置进行解析,转换为内部的规则对象。
  5. 注册规则: 客户端应用程序将解析后的规则注册到 Sentinel 中的规则管理模块。
  6. 实时监控和限流处理: 客户端应用程序在运行时实时监控流量情况,根据规则配置进行限流、降级等操作。当请求达到限流条件时,Sentinel 会根据规则进行相应的限流处理。
  7. 动态刷新: 如果在 Sentinel 控制台上更新了规则,控制台会将新的规则配置推送到 Nacos 配置中心。客户端应用程序通过 Nacos 配置监听机制感知配置变更并获取到最新的规则配置,然后进行动态刷新。
  8. 实时监控与反馈: Sentinel 控制台提供实时的监控界面,您可以在控制台上查看应用程序的流量、规则生效情况等数据,以及对异常情况进行相应的处理。

sentinel执行规则

Sentinel 执行规则的过程是基于 AOP(面向切面编程)和动态代理的机制。当应用程序的资源被访问时,Sentinel 会拦截这些访问,根据事先配置的规则进行判断和处理,从而实现流量控制、降级等功能。以下是 Sentinel 如何执行规则的一般流程:

  1. 定义资源: 在应用程序中,您需要定义要受到限流、降级等规则控制的资源,比如方法、API、URL 等。
  2. 规则配置: 使用 Sentinel 控制台或通过代码方式,在规则管理中配置资源对应的限流、降级等规则,定义规则的类型、条件、策略等信息。
  3. 切入点拦截: Sentinel 使用 AOP 技术拦截您定义的资源访问切入点。这意味着当应用程序的资源被访问时,Sentinel 会拦截这些访问并进行处理。
  4. 规则匹配: Sentinel 根据请求的资源信息,匹配事先配置的规则,判断请求是否满足规则条件,如是否超出了限流阈值。
  5. 限流处理: 如果请求满足规则条件,Sentinel 将执行规则配置中指定的限流策略,比如拒绝请求、返回预设错误信息等。
  6. 降级处理: 如果资源的请求达到降级条件,Sentinel 将执行降级策略,返回降级后的响应,避免影响系统的整体稳定性。
  7. 统计和监控: Sentinel 会根据请求的情况进行统计,生成流量、规则生效情况等监控数据,供您在 Sentinel 控制台上查看。
  8. 动态刷新: Sentinel 允许您在运行时动态调整规则配置,从而实现灵活的流量控制和降级策略。

总体而言,Sentinel 在应用程序运行时,通过拦截资源的访问切入点,根据预先配置的规则进行匹配和处理,从而实现流量控制、降级等功能。

问题

问题一:sentinel熔断降级与自定义异常拦截冲突

项目中使用了自定义全局异常处理,而异常数或者异常比例的统计在

1
com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor.afterCompletion

这个方法执行,自定义全局异常的处理会先于

1
com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor.afterCompletion

这个方法执行执行,因为我们在全局异常里面已经对异常进行处理,比如转换为一个对象,这样导致AbstractSentinelInterceptor.afterCompletion无法获取到异常,进而无法统计异常数或者异常比例。

解决办法

全局异常修改:

1、引入jar包

1
2
3
4
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>

2、在@ExceptionHandler方法中使用Tracer.trace(e)方法将异常信息传递给Sentinel进行统计

1
2
3
4
5
6
7
8
9
10
11
/**
* 拦截所有基础全局异常
*/
@ExceptionHandler(PromptException.class)
public Result baseException(PromptException e) {
// 记录异常信息到Sentinel中
Tracer.trace(e);
log.warn("【全局异常拦截】PromptException: 状态码 {}, 异常信息 {}", e.msgCode().getCodeValue(), e.msgCode().getMsgLog());
simpleLogPromptException(e);
return Result.promptInstance(e);
}

问题二:热点规则不生效

web埋点如果以url作为资源名,规则不生效

解决方法

  1. 注意是否使用了@SentinelResource注解定义的name作为资源名

  2. 配置热点规则配置@SentinelResource后,可能还会出现

    1
    java.lang.reflect.UndeclaredThrowableException: null

    需要在方法中添加throws BlockException或添加blockHandler来处理异常