Sentinel 入门教程 - 基础篇(2020 年)

大纲

前言

本文针对 Sentinel 1.8.0 及以上版本编写,特别说明除外。由于 1.8.0 版本对熔断降级特性进行了全新的改进升级,建议使用最新版本以更好地利用熔断降级的能力。

分布式服务概念

服务雪崩

多个微服务之间调用的时候,假设微服务 A 调用微服务 B,微服务 B 又调用了微服务 C,微服务 C 又调用其它的微服务,这就是所谓的 “扇出”。如果扇出的链路上微服务 B 的调用响应时间过长或者服务不可用,那么对微服务 A 的调用就会占用越来越多的系统资源,进而引起系统崩溃,也就是所谓的 “雪崩效应”。对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内耗尽。比失败更糟糕的是,这些应用程序还可能导致服务之间的调用延迟增加,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的调用失败,不能影响整个应用程序或系统。所以,通常当发现一个模块下的某个实例出现问题后,这时候这个模块往往还会接收到流量,然后这个有问题的模块还被其他的模块调用了,这样就会发生级联故障,或者叫服务雪崩。复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

服务熔断

在分布式与微服务系统中,如果下游服务(服务提供者)因为访问压力过大导致响应很慢或者一直调用失败时,上游服务(服务消费者)为了保证系统的整体可用性,会暂时断开与下游服务(服务提供者)的调用连接。这种处理方式就是熔断,类比保险丝达到最大服务访问后,直接拒绝访问,拉闸断电,然后调用服务降级的方法并返回友好的提示结果。服务熔断一般情况下会有三种状态:闭合、开启、半熔断:

  • 闭合状态(保险丝闭合通电):服务一切正常,没有故障时,上游服务调用下游服务时,不会有任何限制。
  • 开启状态(保险丝断开限电):上游服务不再调用下游服务的接口,会直接返回上游服务中预设的降级方法。
  • 半熔断状态:断路器处于开启状态时,上游服务会根据一定的规则,尝试恢复对下游服务的调用。此时,上游服务会以有限的流量来调用下游服务,同时还会监控调用的成功率。如果成功率达到预期,则断路器会进入闭合状态,恢复服务的调用。如果未达到预期,则断路器会重新进入开启状态。

提示

当整个系统采用分布式和微服务架构后,系统被拆分成一个个小服务,这样就会存在服务与服务之间互相调用的现象,从而形成一个个调用链。形成调用链关系的两个服务中,主动调用其他服务接口的服务处于调用链的上游,提供接口供其他服务调用的服务处于调用链的下游。

服务降级

服务降级,说白了就是一种服务托底方案,如果服务无法完成正常的调用流程,就使用默认的托底方案来返回响应数据。例如,在商品详情页一般都会展示商品的介绍信息,一旦商品详情页系统出现故障无法调用时,那就会直接获取缓存中的商品介绍信息返回给前端页面。值得一提的是,服务降级通常是在服务调用方(服务消费者)一侧实现的,而且是是在触发服务熔断后才执行。

服务限流

服务限流就是限制涌入系统的流量,以防止涌入系统的流量过大而压垮系统。其主要的作用就是保护服务节点或者集群后面的数据节点,防止瞬时流量过大使服务节点和数据节点崩溃(比如前端缓存大量实效),造成系统不可用。服务限流还可用于平滑请求,类似秒杀高并发等操作,严禁一窝蜂地过来拥挤,让请求排好队,一秒钟放行 N 个,然后有序进行处理请求。限流算法有两种,一种就是简单的请求总量计数,一种就是时间窗口限流(一般为 1 秒),如令牌桶算法和漏牌桶算法就是时间窗口的限流算法。

服务隔离

服务隔离有点类似于系统的垂直拆分,就按照一定的规则将系统划分成多个服务模块,并且每个服务模块之间是互相独立的,不会存在强依赖的关系。如果某个拆分后的服务发生故障后,能够将故障产生的影响限制在某个具体的服务内,不会向其他服务扩散,自然也就不会对整体服务产生致命的影响。 互联网行业常用的服务隔离方式有:信号量隔离和线程池隔离。

服务超时

整个系统采用分布式和微服务架构后,系统被拆分成一个个小服务,就会存在服务与服务之间互相调用的现象,从而形成一个个调用链。形成调用链关系的两个服务中,主动调用其他服务接口的服务处于调用链的上游,提供接口供其他服务调用的服务处于调用链的下游。服务超时就是在上游服务调用下游服务时,设置一个最大响应时间,如果超过这个最大响应时间下游服务还未返回响应结果,则断开上游服务与下游服务之间的请求连接,以此释放资源。

流量控制与熔断降级

流量控制概述

拿旅游景点举个示例,旅游景点通常都会有最大的接待量,不可能无限制的放游客进入,比如故宫每天只卖八万张票,超过八万的游客,无法买票进入,因为如果超过八万人,景点的工作人员可能就忙不过来,过于拥挤的景点也会影响游客的体验和心情,并且还会有安全隐患;只卖 N 张票,这就是一种限流的手段。流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。在网络传输时,任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的,因此需要根据系统的处理能力对流量进行控制。

sentinel-flow-control

熔断降级概述

在调用系统的时候,如果调用链路中的某个资源出现了不稳定或者不可用,最终会导致请求发生积压(如下图),而熔断降级就可以解决这个问题。所谓的熔断降级就是当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间过长或者异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联故障(服务雪崩)。

sentinel-service-avalanche

开源实现方案介绍

Hystrix

Hystrix 是由 Netflix 开源的一个针对分布式系统容错处理的开源组件,2011 - 2012 年相继诞生和成熟,在 2018 年 11 月 20 日之后已经停止维护,最后一个正式版本为 1.5.18。Hystrix 单词意为 “豪猪”,浑身有刺保护自己,Hystrix 就是这样一个用来捍卫应用程序健康的利器。进一步说,Hystrix 是一个延迟和容错库,用在隔离远程系统、服务和第三方库,阻止级连故障,在复杂的分布式系统中实现恢复能力,以提高分布式系统的弹性。

Sentinel

Sentinel 是阿里巴巴出品的面向分布式服务架构的轻量级流量控制组件,主要以流量为入点,从限流、流量整形、熔断降级、系统负载保护等多个维度来保障微服务的稳定性。

Resilience4j

Resilience4j 是一款轻量级,易于使用的容错库,其灵感来自于 Netflix Hystrix,但是专为 Java 8 和函数式编程而设计。轻量级,因为库只使用了 Vavr,它没有任何其他外部依赖下。相比之下,Netflix Hystrix 对 Archaius 具有编译依赖性,Archaius 具有更多的外部库依赖性,例如 Guava 和 Apache Commons Configuration。在 Spring Cloud Greenwich 版中,Spring 官方推荐使用 Resilience4j 替代 Hystrix。

开源实现方案对比

sentinel-hystrix-resilience4j-vs

Sentinel 与 Hystrix 对比

Sentinel 介绍

Sentinel 的简介

Sentinel 是阿里巴巴出品的面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。更多介绍可参考:Sentinel 官网Sentinel 项目Sentinel 官方中文文档

sentinel-function

Sentinel 的历史

2012 年,Sentinel 诞生,主要功能为入口流量控制
2013 - 2017 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景,Sentinel 也因此积累了大量的流量归整场景以及生产实践
2018 年,Sentinel 开源,并持续演进
2019 年 Sentinel 朝着多语言扩展的方向不断探索,推出 C++ 原生版本,同时针对 Service Mesh 场景也推出了 Envoy 集群流量控制支持,以解决 Service Mesh 架构下多语言限流的问题
2020 年,推出 Sentinel 的 Go 原生版本,继续朝着云原生的方向演进,同时已覆盖微服务、API Gateway 和 Service Mesh 三大板块的核心生态

Sentinel 的组成

Sentinel 分为两个部分:

  • 核心库(Java 客户端):不依赖任何框架 / 库,能够运行于所有 Java 运行时环境,同时对 Dubbo、Spring Cloud、Spring Cloud Alibaba 等框架也有较好的支持。
  • 控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等。

提示

Sentinel 社区正在将流量治理相关标准抽出到 OpenSergo 标准中,Sentinel 作为流量治理标准实现。有关 Sentinel 流控降级与容错 Spec 的最新进展,请参考 opensergo-specification

Sentinel 的优势

  • 友好的控制面板,支持实时监控
  • 多种限流。支持 QPS 限流,线程数限流,多种限流策略,如:直接拒绝,冷启动,匀速模式(漏斗)
  • 多种降级模式,支持按平均返回时间降级,按多种异常数降级,按异常比率降级
  • 方便扩展开发,支持 SPI 模式对 Chain 进行扩展
  • 支持链路的关联,按链路统计限流,系统保护,热门资源保护等等

Sentinel 的特点

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架 / 库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
  • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

sentinel-special

Sentinel 的开源生态

sentinel-ecology

AHAS Sentinel 控制台

AHAS Sentinel 简介

AHAS Sentinel 是 Sentinel 的阿里云上版本(商业版),提供企业级的高可用防护服务,包括:

  • 可靠的实时监控和历史秒级监控数据查询,包含 QPS、RT、load、CPU 使用率等指标,支持按照调用类型分类,支持同比 / 环比展示
  • 热力图概览,可以快速定位不稳定的机器
  • 动态规则管理 / 推送,无需自行配置外部数据源
  • 告警中心(触发流控、CPU 利用率高等事件)
  • 全自动托管、高可用的集群流量控制
  • 针对 Istio/Envoy 集群的 Mesh 高可用防护
  • Nginx 网关流控

AHAS Sentinel 控制台体验

这里只是简单使用 AHAS Sentinel 官方提供的 Demo 包接入到 AHAS Sentinel 控制台,若希望将已有的 Sentinel 项目接入到 AHAS Sentinel 控制台,具体可参考 Sentinel 官方文档

阿里云开通 AHAS

  • 打开 AHAS 产品主页
  • 在页面右上角单击登录
  • 在页面上输入您的阿里云账号和密码,并单击登录
  • 在产品主页上单击申请免费开通,然后在云产品开通页页面上勾选” 我已阅读并同意《应用高可用服务服务协议》”,并单击立即开通

接入新应用

若应用运行在非阿里云 ECS 环境或本地,需要在左上角选择切换公网环境

ahas-sentinel-2

获取 Demo 包

点击控制台左侧菜单栏的 应用防护,找到 Tab 页面选择 JAVA 语言 -> 体验 Demo,然后根据页面提示下载 Demo 包

ahas-sentinel-1

启动 Demo 应用

公网和阿里云经典网络环境下,需要额外指定 License 用于身份校验,VPC 专有网络无需配置 License

1
2
# 启动命令
$ java -Dahas.namespace=default -Dproject.name=AppName -Dahas.license=xxxxxxxxxxxxx -jar ahas-sentinel-sdk-demo.jar

等待一会,AHAS Sentinel 控制台就会显示相关监控数据

ahas-sentinel-3

Sentinel 基础理念

Sentinel 基本概念

资源

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序自身提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

规则

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

Sentinel 设计理念

流量控制设计理念

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

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

熔断降级设计理念

Sentinel 和 Hystrix 的原则是一致的,即当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间过长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。但在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。Hystrix 通过线程池隔离的方式,来对依赖(在 Sentinel 的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配,并且对于一些使用了 ThreadLocal 的场景来说会有问题(如 Spring 的事务)。Sentinel 对这个问题采取了以下两种手段来解决:

通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝,堆积的线程完成任务后才开始继续接收请求。

针对慢调用和异常对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以根据响应时间和异常等不稳定因素来快速对不稳定的调用进行熔断。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新渐进式地恢复。

系统自适应保护理念

Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

Sentinel 流量控制入门案例

版本说明

本案例使用的 Spring Boot 版本为 2.1.4.RELEASE,Sentinel 版本为 1.8.0,点击下载完整的案例代码。

本地 Sentinel 控制台搭建

Sentinel 提供了一个轻量级的开源控制台,它提供机器发现以及健康状况管理、实时监控(单机和集群),规则管理和推送功能

下载 Sentinel 控制台

Sentinel 控制台下载有两种方式,一种是直接下载编译好的 Release 版本程序包,另一种是下载 Sentinel 控制台的工程源码,在本地打包后启动,这里采用第一种方式

1
2
# 下载命令
$ wget https://github.com/alibaba/Sentinel/releases/download/v1.8.0/sentinel-dashboard-1.8.0.jar

启动 Sentinel 控制台

启动 Sentinel 控制台需要依赖 JDK 版本为 1.8 及以上版本,使用以下命令启动控制台:

1
$ java -Dserver.port=9000 -jar sentinel-dashboard-1.8.0.jar

浏览器访问 http://127.0.0.1:9000,默认登录的用户名和密码为:sentinel/sentinel

sentinel-dashboard-ui-1

Sentinel 控制台启动参数说明

Sentinel 控制台启动时,可配置的 JVM 参数如下:

  • -Dserver.port 指定 Sentinel 控制台监听的端口
  • -Dproject.name,设置应用在 Sentinel 控制台中显示的名称
  • -Dcsp.sentinel.dashboard.server 设置应用需要连接到的 Sentinel 控制台的主机地址和端口号
  • -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456
  • -Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel
  • -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 Session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟

特别注意:Sentinel 控制台启动时,若在 JVM 参数中添加了 -Dproject.name-Dcsp.sentinel.dashboard.server,那么 Sentinel 控制台自身也可以注册到其他 Sentinel 控制台中,Sentinel 控制台甚至可以自己监控自己,启动配置示例如下:

1
$ java -Dserver.port=9000 -Dcsp.sentinel.dashboard.server=127.0.0.1:9000 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar

此时浏览器访问 http://127.0.0.1:9000,可以发现控制台会多出一个 sentinel-dashboard 节点:

sentinel-dashboard-ui

构建 Sentinel 本地应用

引入 Maven 依赖

由于需要将应用接入到 Sentinel 控制台,因此引入了 sentinel-transport-simple-http 依赖

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
37
38
39
40
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<sentinel.version>1.8.0</sentinel.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${sentinel.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

添加 Java SDK 代码

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
37
38
39
40
41
42
@RestController
public class HelloController {

/**
* 资源名称
*/
private static final String RESOURCE_NAME = "Hello";

@GetMapping("/hello")
public String hello() {
// 使用流控规则
try (Entry entry = SphU.entry(RESOURCE_NAME)) {
// 被保护的资源
return "Hello Sentinel!";
} catch (Exception e) {
// 被限流
e.printStackTrace();
return "系统繁忙,请稍后 ...";
}
}

/**
* 当前类的构造函数执行之后执行此方法
*/
@PostConstruct
public void initFlowRules() {
// 创建存放流控规则的集合
List<FlowRule> rules = new ArrayList<>();
// 创建流控规则
FlowRule rule = new FlowRule();
// 定义资源,表示Sentinel会对哪个资源生效
rule.setResource(RESOURCE_NAME);
// 定义流控规则的类型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 定义QPS每秒能通过的请求数
rule.setCount(2);
// 将流控规则存放在集合中
rules.add(rule);
// 加载流控规则
FlowRuleManager.loadRules(rules);
}
}
1
2
3
4
5
6
7
@SpringBootApplication
public class SentinelApplication {

public static void main(String[] args) {
SpringApplication.run(SentinelApplication.class, args);
}
}

将应用连接到 Sentinel 控制台

若应用程序需要连接到 Sentinel 控制台, Sentinel 提供如下两种常用的配置方式,具体可参考 Sentinel 官方文档中的启动配置项

  • JVM -D 参数方式
  • properties 文件方式(1.7.0 版本开始支持)

这里采用添加 JVM 参数的启动方式,即启动应用时加入以下 JVM 参数:

  • -Dproject.name=sentinel-demo,设置本地应用在 Sentinel 控制台中显示的名称
  • -Dcsp.sentinel.dashboard.server=127.0.0.1:9000,设置应用需要连接到的 Sentinel 控制台的主机地址和端口号

或者将 JVM 参数添加到 IDEA Configuration 里的 VM options 中:

sentinel-idea-jvm-options

测试案例代码

  • 1)启动本地的 Sentinel 控制台,命令如下:
1
$ java -Dserver.port=9000 -jar sentinel-dashboard-1.8.0.jar
  • 2)在 Spring Boot 应用的 JVM 参数中配置 Sentinel 控制台,然后启动应用,若控制台输出以下日志信息,则说明 Sentinel 加载成功
1
2
3
4
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: /root/logs/csp/
INFO: Sentinel log name use pid is: false

特别注意

当代码里硬编码了流控规则(即使用 Java API 定义和加载流控规则)时,IDE 的控制台才会在应用启动时输出上面 Sentinel 相关的日志信息。

  • 3)浏览器访问 http://127.0.0.:9090,查看 Sentinel 控制台的监控信息;这里需要先手动调用一次 http://127.0.0.1:8080/hello 接口,Sentinel 控制台才会显示监控数据

sentinel-dashboard-ui-2

  • 4)浏览器访问 http://127.0.0.1:8080/hello,当快速刷新页面时,请求的响应结果变为 系统繁忙,请稍后 ...,则说明 Sentinel 的流控规则生效了

特别注意

  • Sentinel 默认采用懒加载,具体表现为即使微服务应用接入了 Sentinel 的控制台,刚开始在 Sentinl 控制台的页面上也不会看到该微服务应用的信息。
  • 简而言之,想使用 Sentinel 对某个接口进行限流和降级操作,则必须先调用一下对应的接口,使 Sentinel 检测到对应的接口。

动态配置 Sentinel 的流控规则

在上述案例中,将 Sentinel 的流控规则硬编码在 Java 代码里,但在实际的企业项目开发中,这种方式不推荐使用。在日常测试和演示中,一般都会在 Sentinel 控制台里动态配置流控规则,因为这样使用起来比较灵活。首先,将上述案例中添加 Sentinel 流控规则的代码注释掉(示例代码如下),然后在 Spring Boot 应用的 JVM 参数中配置 Sentinel 控制台。重新启动应用后,此时打印的启动日志信息不会再有 Sentinel 相关的内容。特别注意,默认情况下通过 Sentinel 控制台动态添加的规则配置是存放在内存里的,即动态添加的规则配置在 Sentinel 控制台应用重启后会失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class HelloController {

/**
* 资源名称
*/
private static final String RESOURCE_NAME = "Hello";

@GetMapping("/hello")
public String hello() {
// 使用流控规则
try (Entry entry = SphU.entry(RESOURCE_NAME)) {
// 被保护的资源
return "Hello Sentinel!";
} catch (Exception e) {
// 被限流
e.printStackTrace();
return "系统繁忙,请稍后 ...";
}
}
}

浏览器手动调用一次 http://127.0.0.1:8080/hello 接口,然后打开 Sentinel 控制台,动态添加流控规则,表单里的资源名必须与 Java 代码里指定的资源名一致,如下图所示:

sentinel-add-flowrule

浏览器再次访问 http://127.0.0.1:8080/hello,当快速刷新页面时,请求的响应结果变为 系统繁忙,请稍后 ...,则说明动态配置的 Sentinel 流控规则生效了

资源名的配置

  • 在 Sentinel 的控制台里,针对上面的示例代码,也可以在新增流控规则时,将资源名指定为 /hello
  • 资源名指的是资源的唯一名称,默认就是请求的接口路径(URL),可以自行修改。同一微服务应用内,资源名必须唯一。不同微服务应用之间,资源名可以重复。

Sentinel 定义资源的方式

资源是 Sentinel 的关键概念,它可以是 Java 应用程序中的任何内容,例如,由应用程序自身提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。使用 Sentinel 来进行资源保护,主要分为两个步骤,包括定义资源和定义规则。先把可能需要保护的资源定义好,之后再配置规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就可以将之定义为一个资源。


Sentinel 除了基本的定义资源的方式之外,还有其他定义资源的方式,具体如下:

  • 抛出异常的方式定义资源
  • 返回布尔值方式定义资源
  • 异步调用支持
  • 注解方式定义资源
  • 主流框架的默认适配

版本声明

本案例使用的 Spring Boot 版本为 2.1.4.RELEASE,Spring Cloud Alibaba Sentinel 版本为 2.1.3.RELEASE,Sentinel 1.8.0。以下代码,默认都通过 Sentinel 控制台动态配置流控规则来测试,具体不再累述,点击下载完整的案例代码。

添加 Maven 依赖

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud-starter-sentinel>2.1.3.RELEASE</spring-cloud-starter-sentinel>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud-starter-sentinel}</version>
</dependency>
</dependencies>

如果不需要使用注解的方式来定义 Sentinel 资源,一般只需要引入以下两个依赖即可,此时启动应用时需要添加 JVM 参数来连接 Sentinel 控制台。否则需要引入 spring-cloud-starter-alibaba-sentinel 依赖,才能让 @SentinelResource 注解生效。

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>

配置 application.yml

1
2
3
4
5
6
7
8
9
10
server:
port: 8080

spring:
application:
name: sentinel-resource-define-demo
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:9000

抛出异常的方式定义资源

Sentinel 的 SphU 包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后就会抛出 BlockException 异常。这个时候可以捕获异常,进行限流之后的逻辑处理,而在上述的入门案例中就使用了此种方式进行定义资源,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class TestController {

/**
* 资源名称
*/
private static final String RESOURCE_NAME = "Hello";

@GetMapping("/hello")
public String hello() {
// 使用流控规则
try (Entry entry = SphU.entry(RESOURCE_NAME)) {
// 被保护的资源
return "Hello Sentinel!";
} catch (Exception e) {
// 被限流
e.printStackTrace();
return "系统繁忙,请稍后 ...";
}
}
}

返回布尔值方式定义资源

Sentinel 的 SphO 提供 if-else 风格的 API,用这种方式,当资源发生了限流之后就会返回 false,这个时候可以根据返回值,进行限流之后的逻辑处理。

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
@RestController
public class TestBooleanController {

/**
* 资源名称
*/
private static final String RESOURCE_NAME = "Boolean";

@GetMapping("/boolean")
public boolean hello() {
// 使用流控规则
if (SphO.entry(RESOURCE_NAME)) {
// 被保护的资源
try {
System.out.println("Hello Sentinel!");
return true;
} finally {
// 限流的出口
SphO.exit();
}
} else {
// 被限流
System.out.println("系统繁忙,请稍后 ...");
return false;
}
}
}

特别注意:SphO.entry() 需要与 SphO.exit() 方法成对出现,否则会导致调用链记录异常,抛出 ErrorEntryFreeException 异常。

异步调用方式定义资源

Sentinel 支持异步调用链路的统计,在异步调用中,需要通过 SphU.asyncEntry() 方法定义资源,并在需要异步的回调函数中调用 exit() 方法。

1
2
3
4
5
6
7
8
9
10
11
/**
* @EnableAsync 启用Spring的异步调用支持
*/
@SpringBootApplication
@EnableAsync
public class SentinelApplication {

public static void main(String[] args) {
SpringApplication.run(SentinelApplication.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class AsyncService {

/**
* @Async 表示异步调用方法
*/
@Async
public void hello() {
System.out.println("start async method ...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end async method ...");
}
}
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
@RestController
public class TestAsyncController {

private static final String RESOURCE_NAME = "Async";

@Autowired
private AsyncService asyncService;

@GetMapping("/async")
public void hello() {
AsyncEntry asyncEntry = null;
try {
// 使用流控规则
asyncEntry = SphU.asyncEntry(RESOURCE_NAME);
// 被保护的资源
asyncService.hello();
} catch (BlockException e) {
// 被限流
e.printStackTrace();
System.out.println("系统繁忙,请稍后 ...");
} finally {
if (asyncEntry != null) {
// 限流的出口
asyncEntry.exit();
}
}
}
}

注解方式定义资源

  • 通过 @SentinelResource 注解的 blockHandler 属性制定具体的限流处理方法
  • 实现处理方法,该方法的传参必须与资源点的传参一样,并且最后必须加上 BlockException 异常参数,同时返回类型也必须一样
  • 从 1.4.0 版本开始,使用注解的方式定义资源,默认支持自动统计业务异常,无需再手动调用 Tracer.trace(ex) 来记录业务异常
  • 更多注解属性说明,可以看这里
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
@RestController
public class TestAnnotationController {

/**
* 资源名称
*/
private static final String RESOURCE_NAME = "Annotation";

/**
* @return
* @SentinelResource 定义资源
* value:资源名称
* blockHandler:限流处理的方法
*/
@SentinelResource(value = RESOURCE_NAME, blockHandler = "exceptionHandler")
@GetMapping("/annotation")
public String hello() {
// 被保护的资源
return "Hello Sentinel!";
}

/**
* 原方法被限流的时候调用此方法
*
* @param e
* @return
*/
public String exceptionHandler(BlockException e) {
e.printStackTrace();
return "系统繁忙,请稍候 ...";
}
}