《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/Sentinel/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labslab-46 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

《Sentinel 极简入门》中,我们简单了解了 Sentinel,并搭建了 Sentinel 控制台。如果还没看的胖友,可以先看看该文的「1. 概述」「2. 控制台」小节。

Sentinel 功能比较强大,同时胖友可能对服务容错可能比较陌生,所以我们跟着示例,一个一个来学习噢。

2. 流量控制

示例代码对应仓库:lab-46-sentinel-demo

在本小节,我们来学习下 Sentinel 的流量控制功能,对应《Sentinel 官方文档 —— 流量控制》文章。

FROM 《Sentinel 官方文档 —— 主页》

流量控制,在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:流量控制

设计理念

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

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

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

下面,我们来搭建一个流量控制的 Spring Boot 示例。

2.1 引入依赖

pom.xml 文件中,引入相关依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-46-sentinel-demo</artifactId>

<dependencies>
<!-- 实现对 SpringMVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Sentinel 核心库 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 接入控制台 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 对 SpringMVC 的支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-webmvc-adapter</artifactId>
<version>1.7.1</version>
</dependency>
</dependencies>

</project>

  • 具体每个依赖的作用,胖友自己认真看下艿艿添加的所有注释噢。

2.2 SpringMvcConfiguration

cn.iocoder.springboot.lab46.sentineldemo.config 包下,创建 SpringMvcConfiguration 配置类,自定义 sentinel-spring-webmvc-adapter 提供的拦截器。代码如下:

@Configuration
public class SpringMvcConfiguration implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
// Add Sentinel interceptor
// addSentinelWebTotalInterceptor(registry);
addSentinelWebInterceptor(registry);
}

private void addSentinelWebInterceptor(InterceptorRegistry registry) {
// <1.1> 创建 SentinelWebMvcConfig 对象
SentinelWebMvcConfig config = new SentinelWebMvcConfig();
config.setHttpMethodSpecify(true); // <1.2> 是否包含请求方法。即基于 URL 创建的资源,是否包含 Method。
// config.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); // <1.3> 设置 BlockException 处理器。

// <2> 添加 SentinelWebInterceptor 拦截器
registry.addInterceptor(new SentinelWebInterceptor(config)).addPathPatterns("/**");
}

private void addSentinelWebTotalInterceptor(InterceptorRegistry registry) {
// <1> 创建 SentinelWebMvcTotalConfig 对象
SentinelWebMvcTotalConfig config = new SentinelWebMvcTotalConfig();

// <2> 添加 SentinelWebTotalInterceptor 拦截器
registry.addInterceptor(new SentinelWebTotalInterceptor(config)).addPathPatterns("/**");
}

}

SentinelWebInterceptor 拦截器,针对每个 URL 进行流量控制。

  • <1.1> 处,创建 SentinelWebMvcConfig 对象,用于作为 SentinelWebInterceptor 拦截器的配置。

  • <1.2> 处,设置 是否包含请求方法。即基于 URL 创建的资源,是否包含 Method。这里有一个非常重要的概念,就是“资源”。

    FROM 《Sentinel 官方文档 —— 主页》

    资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

    只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。 * 对于 SentinelWebInterceptor 拦截器来说,将 URL + Method 作为一个资源,进行流量控制。具体的,可以看看 SentinelWebInterceptor#getResourceName(HttpServletRequest request) 方法的代码。

  • <1.3> 处,设置 BlockException 的处理器。Sentinel 在流量控制时,当请求到达阀值后,会抛出 BlockException 异常。此时,可以通过定义 BlockExceptionHandler 去处理。这里,我们使用 SpringMVC 提供的全局异常处理机制,具体可见「2.3 GlobalExceptionHandler」

  • <2> 处,添加 SentinelWebInterceptor 拦截器到 InterceptorRegistry 中。

SentinelWebTotalInterceptor 拦截器,针对全局 URL 进行流量控制。简单来说,所有 URL 合计流量,全局统一进行控制。

  • <1> 处,创建 SentinelWebMvcTotalConfig 对象,用于作为 SentinelWebTotalInterceptor 拦截器的配置。
  • <2> 处,添加 SentinelWebTotalInterceptor 拦截器到 InterceptorRegistry 中。

2.3 GlobalExceptionHandler

cn.iocoder.springboot.lab46.sentineldemo.web 包下,创建 GlobalExceptionHandler 配置类,自定义 sentinel-spring-webmvc-adapter 提供的拦截器。代码如下:

@ControllerAdvice(basePackages = "cn.iocoder.springboot.lab46.sentineldemo.controller")
public class GlobalExceptionHandler {

@ResponseBody
@ExceptionHandler(value = BlockException.class)
public String blockExceptionHandler(BlockException blockException) {
return "请求过于频繁";
}

}

2.4 DemoController

cn.iocoder.springboot.lab46.sentineldemo.controller 包下,创建 DemoController 类,提供稍后测试流量控制的示例 API。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

@GetMapping("/echo")
public String echo() {
return "echo";
}

@GetMapping("/test")
public String test() {
return "test";
}

}

2.5 Sentinel 配置文件

resources 目录下,创建 Sentinel 自定义的sentinel.properties 配置文件。内容如下:

csp.sentinel.dashboard.server=127.0.0.1:7070

2.6 Application

创建 Application.java 类,配置 @SpringBootApplication 注解即可。代码如下:

@SpringBootApplication
public class Application {

public static void main(String[] args) {
// <X> 设置系统属性 project.name,提供给 Sentinel 读取
System.setProperty("project.name", "demo-application");

// 启动 Spring Boot 应用
SpringApplication.run(Application.class, args);
}

}

  • <X> 处,设置系统属性 project.name,提供给 Sentinel 读取。比较特殊,该配置项无法在 csp.sentinel.dashboard.server 配置文件中设置。

2.7 简单测试

启动 Spring Boot 应用。

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口。因为 Sentinel 客户端是懒加载的。此时,控制台输出日志如下:

INFO: log output type is: file
INFO: log charset is: utf-8
INFO: log base dir is: /Users/yunai/logs/csp/
INFO: log name use pid is: false

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。此时,我们可以看到 demo-application 应用。如下图所示:Sentinel 控制台 - 首页

③ 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口 10 次。然后点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口的请求情况。如下图所示:Sentinel 控制台 - 实时监控

④ 点击 Sentinel 控制台的「簇点链路」菜单,可以看到 GET:/demo/echo 资源。如下图所示:Sentinel 控制台 - 簇点链路

⑤ 点击 GET:/demo/echo 资源所在列的「流控」按钮,弹出「新增流控规则」。填写流控规则,如下图所示:Sentinel 控制台 - 新增流控规则

  • 这里,我们创建的是比较简单的规则,仅允许 GET:/demo/echo 资源被每秒调用一次。更多详细的配置项的说明,胖友后续一定要认真看《Sentinel 官方文档 —— 流量控制》文章,这是 Sentinel 提供的多种规则中最最最常用的一种。

⑥ 点击「新增」按钮,完成流控规则的添加。此时,会自动跳转到「流控规则」菜单。如下图所示:Sentinel 控制台 - 流控规则

⑦ 使用浏览器,访问 http://127.0.0.1:8080/demo/echo 接口两次,会有一次被 Sentinel 流量控制而拒绝,最终返回 "请求过于频繁"

此时,点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:Sentinel 控制台 - 实时监控

3. 熔断降级

示例代码对应仓库:lab-46-sentinel-demo

在本小节,我们来学习下 Sentinel 的流量控制功能,对应《Sentinel 官方文档 —— 熔断降级》文章。

FROM 《Sentinel 官方文档 —— 主页》

除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。熔断降级

设计理念

Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。

Hystrix 通过 线程池隔离 的方式,来对依赖(在 Sentinel 的概念中对应 资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段:

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

2、通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

下面,我们来搭建一个熔断降级的 Spring Boot 示例。本着省时省力(努力偷懒)的原则,我们直接复用「2. 流量控制」小节的 lab-46-sentinel-demo 项目。

3.1 DemoController

DemoController 类中,额外添加 demo/sleep 接口,通过 sleep 100 毫秒,模拟延迟较高的接口。代码如下:

@GetMapping("/sleep")
public String sleep() throws InterruptedException {
Thread.sleep(100L);
return "sleep";
}

3.2 简单测试

重新启动 Spring Boot 应用。

友情提示:咱会发现之前配置的流量控制规则不见了,不要慌,后面会详细述说。

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/sleep 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

然后,点击 Sentinel 控制台的「簇点链路」菜单,可以看到 GET:/demo/sleep 资源。

之后,点击 GET:/demo/sleep 资源所在列的「降级」按钮,弹出「新增降级规则」。填写降级规则,如下图所示:Sentinel 控制台 - 新增降级规则

  • 这里,我们创建的是比较简单的规则,当 GET:/demo/sleep 资源在 5 秒的时间窗口中,如果平均响应时间超过 1 ms,则进行熔断降级。

Sentinel 一共有 3 种方式来衡量资源是否稳定:

FROM 《Sentinel 官方文档 —— 流量控制》

1、平均响应时间 (DEGRADE_GRADE_RT)

当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。
注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

2、异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO)

当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

3、异常数 (DEGRADE_GRADE_EXCEPTION_COUNT)

当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

③ 点击「新增」按钮,完成降级规则的添加。此时,会自动跳转到「降级规则」菜单。如下图所示:Sentinel 控制台 - 降级规则

④ 使用浏览器,访问 http://127.0.0.1:8080/demo/sleep 接口 6 次,就会有被 Sentinel 服务降级而拒绝,最终返回 "请求过于频繁"

此时,点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:Sentinel 控制台 - 实时监控

耐心等待几秒,过了这个时间窗口后,继续访问 http://127.0.0.1:8080/demo/sleep 接口,又可以成功返回了。

4. 热点参数限流

示例代码对应仓库:lab-46-sentinel-demo

在本小节,我们来学习下 Sentinel 的热点参数限流功能,对应《Sentinel 官方文档 —— 热点参数限流》文章。

FROM 《Sentinel 官方文档 —— 热点参数限流》

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。

热点参数限流,会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。热点参数限流

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

下面,我们来搭建一个热点参数限流的 Spring Boot 示例。本着省时省力(努力偷懒)的原则,我们继续复用「2. 流量控制」小节的 lab-46-sentinel-demo 项目。

4.1 引入依赖

pom.xml 文件中,额外引入相关依赖。

<!-- Sentinel 对【热点参数限流】的支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.7.1</version>
</dependency>
<!-- Sentinel 对 Spring AOP 的拓展 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.7.1</version>
</dependency>

  • 引入 sentinel-parameter-flow-control 依赖,实现 Sentinel 对【热点参数限流】的支持。
  • 引入 sentinel-annotation-aspectj 依赖,实现 Sentinel 对 Spring AOP 的推展。稍后我们会使用到 Sentinel 提供的 @SentinelResource 注解声明自定义资源,通过 Spring AOP 拦截该注解的方法,从而实现自定义资源的 Sentinel 的处理逻辑。

4.2 DemoController

DemoController 类中,额外添加 demo/product_info 接口,用于热点参数限流的示例 API。代码如下:

@GetMapping("/product_info")
@SentinelResource("demo_product_info_hot")
public String productInfo(Integer id) {
return "商品编号:" + id;
}

  • 在方法上,我们添加了 @SentinelResource 注解,自定义了 demo_product_info_hot 资源。

为什么不直接使用 sentinel-spring-webmvc-adapter 库,自动给该 demo/product_info 接口生成的 GET:/demo/product_info 呢?

  • 原因:因为 sentinel-spring-webmvc-adapter 库提供的 SentinelWebInterceptor 和 SentinelWebTotalInterceptor 拦截器在调用 Sentinel 客户端时,并未传入参数,所以无法进行热点参数限流。
  • 解决:使用 @SentinelResource 注解,自定义了 demo_product_info_hot 资源。然后,通过 Spring AOP 拦截该方法的调用,实现 Sentinel 的处理逻辑。在本小节中,就是为了热点参数限流。

4.3 简单测试

重新启动 Spring Boot 应用。

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/product_info?id=1 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

然后,点击 Sentinel 控制台的「簇点链路」菜单,可以看到 demo_product_info_hot 资源。

之后,点击 demo_product_info_hot 资源所在列的「热点」按钮,弹出「新增热点规则」。填写热点规则,如下图所示:Sentinel 控制台 - 新增热点规则

  • 这里,我们只设置了参数索引为 0,统计窗口时长为 60 秒,请求最大次数为 10。更多设置,我们继续往下看。

③ 点击「新增」按钮,完成热点规则的添加。此时,会自动跳转到「热点规则」菜单。如下图所示:Sentinel 控制台 - 热点规则

之后,点击 demo_product_info_hot 资源所在列的「编辑」按钮,弹出「编辑热点规则」。填写热点规则,如下图所示:Sentinel 控制台 - 编辑热点规则

  • 这里,我们配置了当第一个参数的值为 1 时,限制在统计窗口中,请求最大次数为 1。

点击「 保存」按钮,完成编辑。

④ 使用浏览器,访问 http://127.0.0.1:8080/demo/product_info?id=1 接口 2 次,就会有被 Sentinel 热点参数限流而拒绝,最终返回 "请求过于频繁"

此时,点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:Sentinel 控制台 - 实时监控

此时,我们访问 http://127.0.0.1:8080/demo/product_info?id=2 接口,不会存在限流的情况。而是在快速访问 10 次,才会被限流。

😈 有一点要特别注意,热点参数限流看起来和「2. 流量控制」基于 QPS 的限流是比较相似的。不过很大的差异是,热点参数限流是针对每个参数,分别计数来限流。举个例子,在当前示例的热点规则下:

详细的,胖友自己可以简单测试下,感受会非常明显哈。

5. 系统自适应限流

示例代码对应仓库:lab-46-sentinel-demo

在本小节,我们来学习下 Sentinel 的系统自适应限流功能,对应《Sentinel 官方文档 —— 系统自适应限流》文章。

FROM 《Sentinel 官方文档 —— 主页》

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

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

下面,我们来搭建一个系统自适应限流的 Spring Boot 示例。本着省时省力(努力偷懒)的原则,我们继续复用「2. 流量控制」小节的 lab-46-sentinel-demo 项目。

5.1 简单测试

重新启动 Spring Boot 应用。

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

然后,点击 Sentinel 控制台的「系统规则」菜单,然后点击右上角「新增系统规则」按钮,弹出「新增系统保护规则」。填写降级规则,如下图所示:Sentinel 控制台 - 新增系统保护规则

  • 这里,为了测试方便,我们创建了一条 CPU 超过 1% 后,自动进行系统限流。

Sentinel 一共有 5 种系统规则:

FROM 《Sentinel 官方文档 —— 系统自适应限流》

1、Load 自适应(仅对 Linux/Unix-like 机器生效)

系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5

2、CPU usage(1.5.0+ 版本)

当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

3、平均 RT

当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。

4、并发线程数

当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

5、入口 QPS

当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

③ 使用浏览器,访问 http://127.0.0.1:8080/demo/echo 接口,直接就被 Sentinel 系统自适应限流而拒绝,返回 "请求过于频繁"

此时,点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:Sentinel 控制台 - 实时监控

6. 黑白名单控制

示例代码对应仓库:lab-46-sentinel-demo

在本小节,我们来学习下 Sentinel 的黑白名单控制功能,对应《Sentinel 官方文档 —— 黑白名单控制》文章。

FROM 《Sentinel 官方文档 —— 黑白名单控制》

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:

  • 若配置白名单则只有请求来源位于白名单内时才可通过;
  • 若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

下面,我们来搭建一个黑白名单控制的 Spring Boot 示例。本着省时省力(努力偷懒)的原则,我们继续复用「2. 流量控制」小节的 lab-46-sentinel-demo 项目。

6.1 SpringMvcConfiguration

SpringMvcConfiguration 配置类的 #addSentinelWebInterceptor(...) 方法中,增加自定义 RequestOriginParser,解析请求来源。代码如下:

// SpringMvcConfiguration#addSentinelWebInterceptor(...)

config.setOriginParser(new RequestOriginParser() { // 设置请求来源解析器。用于黑白名单控制功能。

@Override
public String parseOrigin(HttpServletRequest request) {
// <X> 从 Header 中,获得请求来源
String origin = request.getHeader("s-user");
// <Y> 如果为空,给一个默认的
if (StringUtils.isEmpty(origin)) {
origin = "default";
}
return origin;
}

});

  • <X> 处,我们从请求头的 "s-user" 对应的值,作为请求来源。注意,Sentinel 黑白名单的控制,一般是服务和服务之间的调用。例如说,配置订单服务允许调用用户服务。
  • <Y> 处,我们判断未获得请求来源的时候,设置默认为 default。原因是,Sentinel 提供的 AuthorityRuleChecker 在进行黑白名单控制时,如果请求来源为空,直接就通过了 =。=

6.2 简单测试

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

然后,点击 Sentinel 控制台的「簇点链路」菜单,可以看到 GET:/demo/echo 资源。

之后,点击 GET:/demo/echo 资源所在列的「授权」按钮,弹出「新增授权规则」。填写授权规则,如下图所示:Sentinel 控制台 - 新增授权规则

  • 这里,我们配置 GET:/demo/echo 资源,仅仅允许来源为 test 的请求才可以访问。

③ 点击「新增」按钮,完成授权规则的添加。此时,会自动跳转到「授权规则」菜单。如下图所示:Sentinel 控制台 - 授权规则

④ 使用浏览器,访问 http://127.0.0.1:8080/demo/echo 接口时,就会有被 Sentinel 黑白名单控制而拒绝,最终返回 "请求过于频繁"

此时,点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:Sentinel 控制台 - 实时监控

我们来使用 Postman 来模拟一个来源为 test 的请求,如下图所示:Postman

7. Sentinel 客户端 API

示例代码对应仓库:lab-46-sentinel-demo

为了减少开发的复杂程度,Sentinel 对大部分的主流框架做了适配,例如 SpringMVC、WebFlux、Dubbo、Spring Cloud、RocketMQ 等等。我们只需要引入对应的 sentinel-apache-xxx-adapter 依赖,即可方便地整合 Sentinel。

不过,Sentinel 并不能适配所有框架,此时我们可以使用 Sentinel 客户端 API,手动进行资源的保护。在《Sentinel 官方文档 —— 如何使用》文章的定义资源其它 API 两个小节,详细的介绍了如何使用 Sentinel 客户端 API。

下面,我们来搭建一个 Sentinel 客户端 API 的示例。本着省时省力(努力偷懒)的原则,我们继续复用「2. 流量控制」小节的 lab-46-sentinel-demo 项目。

7.1 DemoController

DemoController 类中,额外添加 demo/entry_demo 接口,在内部使用 Sentinel 客户端 API 来进行资源的保护。代码如下:

@GetMapping("/entry_demo")
public String entryDemo() {
Entry entry = null;
try {
// <1> 访问资源
entry = SphU.entry("entry_demo");

// <2> ... 执行业务逻辑

return "执行成功";
} catch (BlockException ex) { // <3>
return "被拒绝";
} finally {
// <4> 释放资源
if (entry != null) {
entry.exit();
}
}
}

  • 整个逻辑,和我们使用 Java 进行 I/O 操作的代码比较像,通过 try catch finally 经典套路。
  • <1> 处,调用 Sentinel 的 SphU#entry(String name) 方法,访问资源。其中,参数 name 就是在 Sentinel 中定义的资源名。如果访问资源被拒绝,例如说被限流或降级,则会抛出 BlockException 异常。
  • <2> 处,编写具体的业务逻辑代码。
  • <3> 处,处理访问资源被拒绝所抛出的 BlockException 异常。这里,我们是直接返回 "被拒绝" 的字符串。
  • <4> 处,调用 Sentinel 的 Entry#exit() 方法,释放对资源的访问。注意,entry 和 exit 必须成对出现,不然资源一直被持有者。

这里我们编写的示例是比较简单的,推荐胖友后续自己看下 sentinel-spring-webmvc-adapter 提供的 AbstractSentinelInterceptor 拦截器对 Sentinel 客户端 API 的使用。

7.2 简单测试

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/entry_demo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

然后,点击 Sentinel 控制台的「簇点链路」菜单,可以看到 entry_demo 资源。如下图所示:Sentinel 控制台 - 簇点链路

之后,我们给 entry_demo 资源添加一个每秒仅允许调用一次的流控规则。如下图所示:Sentinel 控制台 - 新增流控规则

③ 使用浏览器,访问 http://127.0.0.1:8080/demo/echo 接口两次,会有一次被 Sentinel 流量控制而拒绝,最终返回 "被拒绝"

8. 注解支持

示例代码对应仓库:lab-46-sentinel-demo

「7. Sentinel 客户端 API」小节中,我们使用 Sentinel 客户端 API,手动进行资源的保护。但是我们会发现,对代码的入侵太强,需要将业务逻辑进行修改。因此,Sentinel 提供了 @SentinelResource 注解声明自定义资源,通过 Spring AOP 拦截该注解的方法,自动调用 Sentinel 客户端 API,进行指定资源的保护。

实际上,在「4. 热点参数限流」小节里,已经使用了 @SentinelResource 注解。下面,我们来看看《Sentinel 官方文档 —— 注解支持》对它的介绍:

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了
  • exceptionsToIgnore:里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

下面,我们来搭建一个 Sentinel @SentinelResource 注解的示例。本着省时省力(努力偷懒)的原则,我们继续复用「2. 流量控制」小节的 lab-46-sentinel-demo 项目。

8.1 DemoController

DemoController 类中,额外添加 demo/annotations_demo 接口,使用 @SentinelResource 注解来声明资源的保护。代码如下:

@GetMapping("/annotations_demo")
@SentinelResource(value = "annotations_demo_resource",
blockHandler = "blockHandler",
fallback = "fallback")
public String annotationsDemo(@RequestParam(required = false) Integer id) throws InterruptedException {
if (id == null) {
throw new IllegalArgumentException("id 参数不允许为空");
}
return "success...";
}

// BlockHandler 处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String blockHandler(Integer id, BlockException ex) {
return "block:" + ex.getClass().getSimpleName();
}

// Fallback 处理函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String fallback(Integer id, Throwable throwable) {
return "fallback:" + throwable.getMessage();
}

  • 在方法中,如果未传 id 参数时,抛出 IllegalArgumentException 异常。

  • 在方法上,添加 @SentinelResource 注解,声明资源的保护。可能比较懵逼的是,如果有 blockHandlerfallback 属性都配置的情况下,怎么分配异常呢?实际上,Sentinel 文档中已经提到这个情况的解答

    特别地,若 blockHandlerfallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。 * fallbackblockHandler 的差异点,在于 blockHandler 只能处理 BlockException 异常,fallback 能够处理所有异常。 * 如果都配置的情况下,BlockException 异常分配给 blockHandler 处理,其它异常分配给 fallback 处理。

8.2 简单测试

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/annotations_demo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

然后,点击 Sentinel 控制台的「簇点链路」菜单,可以看到 annotations_demo_resource 资源。如下图所示:Sentinel 控制台 - 簇点链路

之后,我们给 annotations_demo_resource 资源添加一个每 60 秒的异常比例是 10% 的降级规则。如下图所示:Sentinel 控制台 - 新增降级规则

③ 使用浏览器,访问 http://127.0.0.1:8080/demo/annotations_demo 接口,响应结果为 "fallback:id 参数不允许为空"。原因是,因为传入的 id 为空,所以抛出 IllegalArgumentException 异常,最终交给 #fallback(...) 方法处理。

继续不停访问 http://127.0.0.1:8080/demo/annotations_demo 接口,达到在 ② 中配置的降级规则的阀值,会响应结果为 block:DegradeException。原因是,达到降级的阀值后,抛出的是 DegradeException 异常,而该异常是 BlockingException 的子类,所以交给 #blockHandler(...) 方法处理。

9. 规则管理及推送

友情提示:本小节内容会略微难一丢丢,请保持你耐心阅读完。然后,在跟着后续小节的示例,会更加容易理解。

《Sentinel 官方文档 —— 在生产环境中使用 Sentinel》「规则管理及推送」小节,详细的介绍了 Sentinel 规则的管理与推送方式的三种模式。核心内容如下:

FROM 《Sentinel 官方文档 —— 在生产环境中使用 Sentinel》

推送模式 说明 优点 缺点
原始模式 API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource 简单,无任何依赖 不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
Pull 模式 扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等 简单,无任何依赖;规则持久化 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
Push 模式 扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。 规则持久化;一致性;快速 引入第三方依赖
  • 详细的每个模式的说明,一定要认真认真认证看下文档,这对理解接下来的内容,非常重要哟。

9.1 原始模式

可能胖友会和艿艿一开始有相同的理解误区。Sentinel 控制台并不持久化规则,而是通过 sentinel-transport-simple-http 依赖提供的 HTTP API,将我们在 Sentinel 控制台编辑的规则,推送给集成 Sentinel 客户端的应用的内存中。如下图所示:原始模式

  • 因为我们引入了 sentinel-transport-simple-http 依赖,所以应用在启动的时候,会注册到 Sentinel 控制台。因此,我们在 Sentinel 控制台的「机器列表」菜单,可以看到每个应用的示例。如下图所示:Sentinel 控制台 —— 机器列表
  • 同时,sentinel-transport-simple-http 依赖提供了 HTTP API 接口,提供给 Sentinel 进行规则的推送,监控的查询。具体有哪些接口,我们来一起看下,如下图所示:sentinel-transport-simple-http API

这样一个梳理,是不是对原始模式的理解,稍微会清晰一些些了。另外,我们可以参考《Sentinel 官方文档 —— 如何使用》「规则的种类」小节,直接使用代码配置规则,通过调用 FlowRuleManager#loadRules(List<FlowRule> rules) 方法,将 Sentinel 规则加载到内存当中。

9.2 Pull 和 Push 模式

对于 Pull 模式Push 模式,都是由 Sentinel 客户端从不同的数据源,加载配置规则。并不是所有的数据源自身支持实时推送功能,因而导致 Sentinel 的规则推送模式分成非实时的 Pull 模式,和实时的 Push 模式。

《Sentinel 官方文档 —— 动态规则》中,将 Pull 和 Push 模式,统称为动态规则。同时,也提供了每种数据源的使用示例。😈 当然在下文中,我们会搭建在 Spring Boot 项目中的使用示例。

另外,考虑到更方便的配置 Sentinel 规则,需要将 Sentinel 控制台和配置中心等数据源进行集成。具体的,需要我们参考官方如下文档,进行自己实现。

FROM 《Sentinel 官方文档 —— 在生产环境中使用 Sentinel》

从 Sentinel 1.4.0 开始,Sentinel 控制台提供 DynamicRulePublisherDynamicRuleProvider 接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。Sentinel 提供应用维度规则推送的示例页面(/v2/flow),用户改造控制台对接配置中心后可直接通过 v2 页面推送规则至配置中心。改造详情可参考 应用维度规则推送示例

部署多个控制台实例时,通常需要将规则存至 DB 中,规则变更后同步向配置中心推送规则。

10. 使用 Nacos 作为数据源

示例代码对应仓库:lab-46-sentinel-demo-nacos

本小节,我们使用 Nacos 作为 Sentinel 规则的数据源,并使用 Push 模式推送规则。对于 Nacos 不了解的胖友,可以先看看《Nacos 极简入门》文章。

下面,我们从「2. 流量控制」小节的 lab-46-sentinel-demo 项目,复制出 lab-46-sentinel-demo-nacos 项目,改造成接入 Nacos 作为数据源。

10.1 引入依赖

pom.xml 文件中,额外引入相关依赖。

<!-- Sentinel 对 Nacos 作为数据源的支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.7.1</version>
</dependency>

  • 引入 sentinel-datasource-nacos 依赖,实现 Sentinel 对 Nacos 作为数据源的支持。

10.2 NacosDataSource

因为 Sentinel 暂时没有提供 Spring Boot Starter 项目,所以我们需要自己创建 NacosDataSource Bean 对象,实现接入 Nacos 作为数据源。下面,我们参考 spring-cloud-alibaba-sentinel-datasource 项目中关于 Nacos 的代码,在 SentinelConfiguration 配置类中,增加 NacosDataSource Bean 的创建:

@Bean
public NacosDataSource nacosDataSource(ObjectMapper objectMapper) {
// <1> Nacos 配置。这里先写死,推荐后面写到 application.yaml 配置文件中。
String serverAddress = "127.0.0.1:8848"; // Nacos 服务器地址
String namespace = ""; // Nacos 命名空间
String dataId = "demo-application-flow-rule"; // Nacos 配置集编号
// String dataId = "example-sentinel"; // Nacos 配置集编号
String group = "DEFAULT_GROUP"; // Nacos 配置分组

// <2> 创建 NacosDataSource 对象
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverAddress);
properties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
NacosDataSource<List<FlowRule>> nacosDataSource = new NacosDataSource<>(properties, group, dataId,
new Converter<String, List<FlowRule>>() { // <X> 转换器,将读取的 Nacos 配置,转换成 FlowRule 数组
@Override
public List<FlowRule> convert(String value) {
try {
return Arrays.asList(objectMapper.readValue(value, FlowRule[].class));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
});

// 注册到 FlowRuleManager 中
FlowRuleManager.register2Property(nacosDataSource.getProperty());
return nacosDataSource;
}

  • <1> 处,Nacos 配置。这里先写死,推荐后面写到 application.yaml 配置文件中。
  • <2> 处,创建 NacosDataSource 对象,实现 Sentinel 对 Nacos 作为数据源的支持。这里比较难理解的是 <X> 处创建的 Converter 类,它负责将从 Nacos 读取的配置,转换成 FlowRule(流控规则) 数组。
  • <3> 处,注册到FlowRuleManager(流控规则管理器) 中。

另外,如果胖友想要接入其它 Sentinel 规则,需要创建多个 NacosDataSource Bean 对象,并编写对应的 Converter 类。

还有,NacosDataSource 已经实现了 Nacos 配置监听器。也就是说,在应用启动的情况下,如果我们更改了 Nacos 中的配置,应用中的 Sentinel 规则也会立即刷新。

10.3 创建 Nacos 配置集

理论来说,我们需要改造 Sentinel 控制台的代码,将 Sentinel 接入 Nacos 作为规则的数据源。但是考虑到涉及的内容较多,本文暂时跳过,感兴趣的胖友,可以阅读应用维度规则推送示例文章。

咳咳咳,理论来说,Sentinel 控制台应该内置了对 Nacos 数据源的接入。

也因此,我门直接在 Nacos 中,创建一个配置集 demo-application-flow-rule,具体内容如下图:创建 Nacos 配置集

  • 推荐配置集编号的命名规则为 ${applicationName}-${ruleType},例如我们这里是 demo-application-flow-rule,即 demo-application 应用的流控规则。

  • 配置内容中,我们设置了一个针对 GET:/demo/echo 资源,每秒允许访问 5 次。每个字段的说明如下:

    [
    {
    "resource": "GET:/demo/echo",
    "limitApp": "default",
    "grade": 1,
    "count": 5,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
    }
    ]

    FROM 《Sentinel 控制规则 —— 流量控制》 * resource:资源名,即限流规则的作用对象 * count: 限流阈值 * grade: 限流阈值类型(QPS 或并发线程数) * limitApp: 流控针对的调用来源,若为 default 则不区分调用来源 * strategy: 调用关系限流策略 * controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)

10.4 简单测试

启动 Spring Boot 应用。

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

点击 Sentinel 控制台的「流控规则」菜单,可以看到应用中已经有一条流控规则,是从 Nacos 数据源加载而来的。如下图所示:Sentinel 控制台 - 流控规则

③ 使用浏览器,快速访问 http://127.0.0.1:8080/demo/echo 接口 6 次,最后 1 次会被 Sentinel 流量控制而拒绝,最终返回 "请求过于频繁"

11. 使用 Apollo 作为数据源

示例代码对应仓库:lab-46-sentinel-demo-apollo

本小节,我们使用 Apollo 作为 Sentinel 规则的数据源,并使用 Push 模式推送规则。对于 Nacos 不了解的胖友,可以先看看《Apollo 极简入门》文章。

下面,我们从「2. 流量控制」小节的 lab-46-sentinel-demo 项目,复制出 lab-46-sentinel-demo-apollo 项目,改造成接入 Apollo 作为数据源。

11.1 引入依赖

pom.xml 文件中,额外引入相关依赖。

<!-- Sentinel 对 Apollo 作为数据源的支持 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-apollo</artifactId>
<version>1.7.1</version>
</dependency>

  • 引入 sentinel-datasource-apollo 依赖,实现 Sentinel 对 Apollo 作为数据源的支持。

11.2 ApolloDataSource

因为 Sentinel 暂时没有提供 Spring Boot Starter 项目,所以我们需要自己创建 ApolloDataSource Bean 对象,实现接入 Apollo 作为数据源。下面,我们参考 spring-cloud-alibaba-sentinel-datasource 项目中关于 Apollo 的代码,在 SentinelConfiguration 配置类中,增加 ApolloDataSource Bean 的创建:

@Value("${spring.application.name}")
private String applicationName;

@Bean
public ApolloDataSource apolloDataSource(ObjectMapper objectMapper) {
// <1> Apollo 配置。这里先写死,推荐后面写到 application.yaml 配置文件中。
String appId = applicationName; // Apollo 项目编号。一般情况下,推荐和 spring.application.name 保持一致
String serverAddress = "http://localhost:8080"; // Apollo Meta 服务器地址
String namespace = "application"; // Apollo 命名空间
String flowRuleKey = "sentinel.flow-rule"; // Apollo 配置项的 KEY

// <2> 创建 ApolloDataSource 对象
System.setProperty("app.id", appId);
System.setProperty("apollo.meta", serverAddress);
ApolloDataSource<List<FlowRule>> apolloDataSource = new ApolloDataSource<>(namespace, flowRuleKey, "",
new Converter<String, List<FlowRule>>() { // <X> 转换器,将读取的 Apollo 配置,转换成 FlowRule 数组
@Override
public List<FlowRule> convert(String value) {
try {
return Arrays.asList(objectMapper.readValue(value, FlowRule[].class));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
});

// <3> 注册到 FlowRuleManager 中
FlowRuleManager.register2Property(apolloDataSource.getProperty());
return apolloDataSource;
}

  • <1> 处,Apollo 配置。这里先写死,推荐后面写到 application.yaml 配置文件中。注意,这里我们是创建了一个 Apollo 配置项 sentinel.flow-rule,存储 Sentinel 的流控规则。
  • <2> 处,创建 ApolloDataSource 对象,实现 Sentinel 对 Apollo 作为数据源的支持。这里比较难理解的是 <X> 处创建的 Converter 类,它负责将从 Apollo 读取的配置,转换成 FlowRule(流控规则) 数组。
  • <3> 处,注册到FlowRuleManager(流控规则管理器) 中。

另外,如果胖友想要接入其它 Sentinel 规则,需要创建多个 ApolloDataSource Bean 对象,并编写对应的 Converter 类。

还有,ApolloDataSource 已经实现了 Apollo 配置监听器。也就是说,在应用启动的情况下,如果我们更改了 Apollo 中的配置,应用中的 Sentinel 规则也会立即刷新。

11.3 配置文件

创建 application.yaml 配置文件,自定义 Spring Boot 应用端口,避免和本地的 Apollo Config Service 使用的 8080 端口冲突。配置如下:

spring:
application:
name: demo-application

server:
port: 18080 # 避免和 Apollo 使用到的 8080 端口冲突

11.4 创建 Apollo 配置

理论来说,我们需要改造 Sentinel 控制台的代码,将 Sentinel 接入 Apollo 作为规则的数据源。但是考虑到涉及的内容较多,本文暂时跳过,感兴趣的胖友,可以阅读应用维度规则推送示例文章。

咳咳咳,理论来说,Sentinel 控制台应该内置了对 Apollo 数据源的接入。

也因此,我门直接在 Apollo 中,创建一个配置项 sentinel.flow-rule,具体内容如下图:创建 Apollo 配置项

  • 推荐配置项的 Key 的命名规则为 sentinel.${ruleType},例如我们这里是 sentinel.flow-rule,即应用的流控规则。

  • 在配置项的 Value 中,我们设置了一个针对 GET:/demo/echo 资源,每秒允许访问 5 次。每个字段的说明如下:

    [
    {
    "resource": "GET:/demo/echo",
    "limitApp": "default",
    "grade": 1,
    "count": 5,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
    }
    ]

    FROM 《Sentinel 控制规则 —— 流量控制》 * resource:资源名,即限流规则的作用对象 * count: 限流阈值 * grade: 限流阈值类型(QPS 或并发线程数) * limitApp: 流控针对的调用来源,若为 default 则不区分调用来源 * strategy: 调用关系限流策略 * controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)

11.5 简单测试

启动 Spring Boot 应用。

① 使用浏览器,访问下 http://127.0.0.1:18080/demo/echo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

点击 Sentinel 控制台的「流控规则」菜单,可以看到应用中已经有一条流控规则,是从 Apollo 数据源加载而来的。如下图所示:Sentinel 控制台 - 流控规则

③ 使用浏览器,快速访问 http://127.0.0.1:18080/demo/echo 接口 6 次,最后 1 次会被 Sentinel 流量控制而拒绝,最终返回 "请求过于频繁"

12. 使用 File 作为数据源

示例代码对应仓库:lab-46-sentinel-demo-file

本小节,我们使用 File(文件) 作为 Sentinel 规则的数据源,并使用 Pull 模式推送规则。注意,生产环境下,不建议使用 File 作为数据源。

下面,我们从「2. 流量控制」小节的 lab-46-sentinel-demo 项目,复制出 lab-46-sentinel-demo-file 项目,改造成接入 File 作为数据源。

12.1 引入依赖

pom.xml 文件中,额外引入相关依赖。

<!-- Sentinel 对数据源的拓展的基础库,内置了 File 数据源 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
<version>1.7.1</version>
</dependency>

  • 引入 sentinel-datasource-extension 依赖,实现 Sentinel 对 File 作为数据源的支持。

12.2 FileRefreshableDataSource

FileRefreshableDataSource,支持从本地文件读取 Sentinel 规则。同时,通过定时读取实现刷新,默认间隔为 3 秒。

下面,我们在 SentinelConfiguration 配置类中,创建 FileRefreshableDataSource Bean 对象,实现对项目中的 flow-rule.json 配置文件。代码如下:

@Bean
public FileRefreshableDataSource<List<FlowRule>> refreshableDataSource(ObjectMapper objectMapper) throws IOException {
// File 配置。这里先写死,推荐后面写到 application.yaml 配置文件中。
ClassPathResource resource = new ClassPathResource("/flow-rule.json");

// 创建 FileRefreshableDataSource 对象
FileRefreshableDataSource<List<FlowRule>> refreshableDataSource = new FileRefreshableDataSource<>(resource.getFile(),
new Converter<String, List<FlowRule>>() { // <X> 转换器,将读取的文本配置,转换成 FlowRule 数组
@Override
public List<FlowRule> convert(String value) {
try {
return Arrays.asList(objectMapper.readValue(value, FlowRule[].class));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
});

// 注册到 FlowRuleManager 中
FlowRuleManager.register2Property(refreshableDataSource.getProperty());
return refreshableDataSource;
}

  • <1> 处,读取配置文件的地址为 classpath:/flow-rule.json。如下图所示:配置文件

    [
    {
    "resource": "GET:/demo/echo",
    "limitApp": "default",
    "grade": 1,
    "count": 5,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
    }
    ]

    FROM 《Sentinel 控制规则 —— 流量控制》 * resource:资源名,即限流规则的作用对象 * count: 限流阈值 * grade: 限流阈值类型(QPS 或并发线程数) * limitApp: 流控针对的调用来源,若为 default 则不区分调用来源 * strategy: 调用关系限流策略 * controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)

  • <2> 处,创建 FileRefreshableDataSource 对象,实现 Sentinel 对 File 作为数据源的支持。这里比较难理解的是 <X> 处创建的 Converter 类,它负责将从 File 读取的配置,转换成 FlowRule(流控规则) 数组。

  • <3> 处,注册到FlowRuleManager(流控规则管理器) 中。

12.3 简单测试

启动 Spring Boot 应用。

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

点击 Sentinel 控制台的「流控规则」菜单,可以看到应用中已经有一条流控规则,是从 File 数据源加载而来的。如下图所示:Sentinel 控制台 - 流控规则

③ 使用浏览器,快速访问 http://127.0.0.1:8080/demo/echo 接口 6 次,最后 1 次会被 Sentinel 流量控制而拒绝,最终返回 "请求过于频繁"

12.4 FileWritableDataSource

FileWritableDataSource,支持接收 Sentinel 控制的规则推送,写入配置到指定文件。

下面,我们在 SentinelConfiguration 配置类中,创建 FileWritableDataSource Bean 对象,实现接收 Sentinel 控制的规则推送,写入到 ${user.home}/sentinel/${project.name}/flow-rule.json 配置文件中。代码如下:

友情提示:请注释掉「12.2 FileRefreshableDataSource」小节中,创建 FileRefreshableDataSource Bean 的代码。因为在 Sentinel 客户端中,每个 Sentinel 每种规则管理器只允许注册一个 DataSource。

而本小节中,我们还是会注册一个 FileRefreshableDataSource 对象,因此会存在冲突。

@Bean
public FileWritableDataSource writableDataSource(ObjectMapper objectMapper) throws IOException {
// <1> File 配置。这里先写死,推荐后面写到 application.yaml 配置文件中。
String directory = System.getProperty("user.home") + File.separator
+ "sentinel" + File.separator
+ System.getProperty("project.name");
mkdirs(directory);
String path = directory + File.separator + "flow-rule.json";
creteFile(path);

// <2> 创建 FileRefreshableDataSource 对象
FileRefreshableDataSource<List<FlowRule>> refreshableDataSource = new FileRefreshableDataSource<>(path,
new Converter<String, List<FlowRule>>() { // 转换器,将读取的文本配置,转换成 FlowRule 数组
@Override
public List<FlowRule> convert(String value) {
try {
return Arrays.asList(objectMapper.readValue(value, FlowRule[].class));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
});
// 注册到 FlowRuleManager 中
FlowRuleManager.register2Property(refreshableDataSource.getProperty());

// <3> 创建 FileWritableDataSource 对象
FileWritableDataSource<List<FlowRule>> fileWritableDataSource = new FileWritableDataSource<>(path,
new Converter<List<FlowRule>, String>() {
@Override
public String convert(List<FlowRule> source) {
try {
return objectMapper.writeValueAsString(source);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
});

// 注册到 WritableDataSourceRegistry 中
WritableDataSourceRegistry.registerFlowDataSource(fileWritableDataSource);
return fileWritableDataSource;
}

private void mkdirs(String path) {
File file = new File(path);
if (file.exists()) {
return;
}
file.mkdirs();
}

private void creteFile(String path) throws IOException {
File file = new File(path);
if (file.exists()) {
return;
}
file.createNewFile();
}

  • <1> 处,定义 File 配置,并保证 File 一定存在。
  • <2> 处,创建对 File 读取的 FileRefreshableDataSource 对象,并注册到 FlowRuleManager 中。
  • <3> 处,创建对 File 读取的 FileWritableDataSource 对象,并注册到 WritableDataSourceRegistry 中。

12.5 简单测试

重启启动 Spring Boot 应用。

① 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口。因为 Sentinel 客户端是懒加载的。

② 使用浏览器,访问下 http://127.0.0.1:7070/ 地址,进入 Sentinel 控制台。

点击 Sentinel 控制台的「流控规则」菜单,给 GET:/demo/echo 资源新建一条流控规则,如下图所示:Sentinel 控制台 - 新建流控规则

新建完成后,查看 ${user.home}/sentinel/${project.name}/flow-rule.json 配置文件,会发现该流控规则已经存储进来。操作命令行如下:

$ cat /Users/yunai/sentinel/demo-application/flow-rule.json
[{"resource":"GET:/demo/echo","limitApp":"default","grade":1,"count":5.0,"strategy":0,"refResource":null,"controlBehavior":0,"warmUpPeriodSec":10,"maxQueueingTimeMs":500,"clusterMode":false,"clusterConfig":{"flowId":null,"thresholdType":0,"fallbackToLocalWhenFail":true,"strategy":0,"sampleCount":10,"windowIntervalMs":1000}}]

  • 有一点要注意,Sentinel 是按照应用实例创建规则,所以只会推送给一个应用实例。也就是说,如果一个应用我们启动了多个实例,需要配置多次,才能保证每个实例的配置文件,都持久化了该规则。

③ 使用浏览器,快速访问 http://127.0.0.1:8080/demo/echo 接口 6 次,最后 1 次会被 Sentinel 流量控制而拒绝,最终返回 "请求过于频繁"

13. 集群流控

艿艿暂时没有去研究 Sentinel 的集群流控功能,主要看 Token Server 暂时未提供高可用方案,这个上到生产肯定是有蛮大风险的。感兴趣的胖友,可以先阅读如下文章:

666. 彩蛋

Sentinel 的东西真是多呀,所以胖友一定要多看看《Sentinel 官方文档》。本文的示例,大体覆盖到 Sentinel 文档所有的内容,遗漏的章节如下:

Sentinel 支持的数据源比较多,艿艿暂时只写了三个主流的,剩余的胖友可以看看如下示例代码:

文章目录
  1. 1. 1. 概述
  2. 2. 2. 流量控制
    1. 2.1. 2.1 引入依赖
    2. 2.2. 2.2 SpringMvcConfiguration
    3. 2.3. 2.3 GlobalExceptionHandler
    4. 2.4. 2.4 DemoController
    5. 2.5. 2.5 Sentinel 配置文件
    6. 2.6. 2.6 Application
    7. 2.7. 2.7 简单测试
  3. 3. 3. 熔断降级
    1. 3.1. 3.1 DemoController
    2. 3.2. 3.2 简单测试
  4. 4. 4. 热点参数限流
    1. 4.1. 4.1 引入依赖
    2. 4.2. 4.2 DemoController
    3. 4.3. 4.3 简单测试
  5. 5. 5. 系统自适应限流
    1. 5.1. 5.1 简单测试
  6. 6. 6. 黑白名单控制
    1. 6.1. 6.1 SpringMvcConfiguration
    2. 6.2. 6.2 简单测试
  7. 7. 7. Sentinel 客户端 API
    1. 7.1. 7.1 DemoController
    2. 7.2. 7.2 简单测试
  8. 8. 8. 注解支持
    1. 8.1. 8.1 DemoController
    2. 8.2. 8.2 简单测试
  9. 9. 9. 规则管理及推送
    1. 9.1. 9.1 原始模式
    2. 9.2. 9.2 Pull 和 Push 模式
  10. 10. 10. 使用 Nacos 作为数据源
    1. 10.1. 10.1 引入依赖
    2. 10.2. 10.2 NacosDataSource
    3. 10.3. 10.3 创建 Nacos 配置集
    4. 10.4. 10.4 简单测试
  11. 11. 11. 使用 Apollo 作为数据源
    1. 11.1. 11.1 引入依赖
    2. 11.2. 11.2 ApolloDataSource
    3. 11.3. 11.3 配置文件
    4. 11.4. 11.4 创建 Apollo 配置
    5. 11.5. 11.5 简单测试
  12. 12. 12. 使用 File 作为数据源
    1. 12.1. 12.1 引入依赖
    2. 12.2. 12.2 FileRefreshableDataSource
    3. 12.3. 12.3 简单测试
    4. 12.4. 12.4 FileWritableDataSource
    5. 12.5. 12.5 简单测试
  13. 13. 13. 集群流控
  14. 14. 666. 彩蛋