52.Spring Boot 服务容错 Sentinel 入门
52.Spring Boot 服务容错 Sentinel 入门
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("/**");
}
}
#addSentinelWebTotalInterceptor(InterceptorRegistry registry)
方法,添加 SentinelWebTotalInterceptor 拦截器。#addSentinelWebInterceptor(InterceptorRegistry registry)
方法,添加 SentinelWebInterceptor 拦截器。
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 "请求过于频繁";
}
}
- 在
#blockExceptionHandler(...)
方法中,我们处理 BlockException 异常。因为这里是示例,所以处理的比较简单。胖友可以看看《芋道 Spring Boot SpringMVC 入门》的「5. 全局异常处理」小节。
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
csp.sentinel.dashboard.server
配置项,设置 Sentinel 控制台地址。- 更多其它配置项,可见《Sentinel 官方文档 ------ 启动配置项》。
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
应用。如下图所示:
③ 使用浏览器,访问下 http://127.0.0.1:8080/demo/echo 接口 10 次。然后点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口的请求情况。如下图所示:
④ 点击 Sentinel 控制台的「簇点链路」菜单,可以看到 GET:/demo/echo
资源。如下图所示:
⑤ 点击 GET:/demo/echo
资源所在列的「流控」按钮,弹出「新增流控规则 」。填写流控规则,如下图所示:
- 这里,我们创建的是比较简单的规则,仅允许
GET:/demo/echo
资源被每秒调用一次。更多详细的配置项的说明,胖友后续一定要认真看《Sentinel 官方文档 ------ 流量控制》文章,这是 Sentinel 提供的多种规则中最最最常用的一种。
⑥ 点击「新增」按钮,完成流控规则的添加。此时,会自动跳转到「流控规则」菜单。如下图所示:
⑦ 使用浏览器,访问 http://127.0.0.1:8080/demo/echo 接口两次,会有一次被 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
资源所在列的「降级」按钮,弹出「新增降级规则 」。填写降级规则,如下图所示:
- 这里,我们创建的是比较简单的规则,当
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,则结束熔断状态后仍可能再进入熔断状态。
③ 点击「新增」按钮,完成降级规则的添加。此时,会自动跳转到「降级规则」菜单。如下图所示:
④ 使用浏览器,访问 http://127.0.0.1:8080/demo/sleep 接口 6 次,就会有被 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
资源所在列的「热点」按钮,弹出「新增热点规则 」。填写热点规则,如下图所示:
- 这里,我们只设置了参数索引为 0,统计窗口时长为 60 秒,请求最大次数为 10。更多设置,我们继续往下看。
③ 点击「新增」按钮,完成热点规则的添加。此时,会自动跳转到「热点规则」菜单。如下图所示:
之后,点击 demo_product_info_hot
资源所在列的「编辑」按钮,弹出「编辑热点规则 」。填写热点规则,如下图所示:
- 这里,我们配置了当第一个参数的值为 1 时,限制在统计窗口中,请求最大次数为 1。
点击「 保存」按钮,完成编辑。
④ 使用浏览器,访问 http://127.0.0.1:8080/demo/product_info?id=1 接口 2 次,就会有被 Sentinel 热点参数限流而拒绝,最终返回 "请求过于频繁"
。
此时,点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:
此时,我们访问 http://127.0.0.1:8080/demo/product_info?id=2 接口,不会存在限流的情况。而是在快速访问 10 次,才会被限流。
😈 有一点要特别注意,热点参数限流看起来和「2. 流量控制」基于 QPS 的限流是比较相似的。不过很大的差异是,热点参数限流是针对每个参数,分别计数来限流。举个例子,在当前示例的热点规则下:
- 针对每个
id
对应的 [http://127.0.0.1:8080/demo/product_info?id= %7Bid%7D) 接口,在每 60 秒内,分别允许访问 10 次。 - 针对
id = 1
的情况,作为特殊(例外)配置,在每 60 秒内,仅仅允许访问 1 次。
详细的,胖友自己可以简单测试下,感受会非常明显哈。
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 控制台的「系统规则」菜单,然后点击右上角「新增系统规则」按钮,弹出「新增系统保护规则 」。填写降级规则,如下图所示:
- 这里,为了测试方便,我们创建了一条 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 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:
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
资源所在列的「授权」按钮,弹出「新增授权规则 」。填写授权规则,如下图所示:
- 这里,我们配置
GET:/demo/echo
资源,仅仅允许来源为test
的请求才可以访问。
③ 点击「新增」按钮,完成授权规则的添加。此时,会自动跳转到「授权规则」菜单。如下图所示:
④ 使用浏览器,访问 http://127.0.0.1:8080/demo/echo 接口时,就会有被 Sentinel 黑白名单控制而拒绝,最终返回 "请求过于频繁"
。
此时,点击 Sentinel 控制台的「实时监控」菜单,可以看到该接口被拒绝的统计。如下图所示:
我们来使用 Postman 来模拟一个来源为 test
的请求,如下图所示:
7. Sentinel 客户端 API
示例代码对应仓库:lab-46-sentinel-demo。
为了减少开发的复杂程度,Sentinel 对大部分的主流框架做了适配,例如 SpringMVC、WebFlux、Dubbo、Spring Cloud、RocketMQ 等等。我们只需要引入对应的 sentinel-apache-xxx-adapter
依赖,即可方便地整合 Sentinel。
- 在上述的示例中,我们使用的就是 Sentinel 提供的
sentinel-spring-webmvc-adapter
对 SpringMVC 的示例。 - 更多 Sentinel 的适配框架介绍,可见《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
资源。如下图所示:
之后,我们给 entry_demo
资源添加一个每秒仅允许调用一次的流控规则。如下图所示:
③ 使用浏览器,访问 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
处理逻辑。若未配置 blockHandler
、fallback
和 defaultFallback
,则被限流降级时会将 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
注解,声明资源的保护。可能比较懵逼的是,如果有blockHandler
和fallback
属性都配置的情况下,怎么分配异常呢?实际上,Sentinel 文档中已经提到这个情况的解答特别地,若
blockHandler
和fallback
都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。 *fallback
和blockHandler
的差异点,在于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
资源。如下图所示:
之后,我们给 annotations_demo_resource
资源添加一个每 60 秒的异常比例是 10% 的降级规则。如下图所示:
③ 使用浏览器,访问 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 规则的管理与推送方式的三种模式。核心内容如下:
推送模式 | 说明 | 优点 | 缺点 |
---|---|---|---|
原始模式 | 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-transport-simple-http
依赖提供了 HTTP API 接口,提供给 Sentinel 进行规则的推送,监控的查询。具体有哪些接口,我们来一起看下,如下图所示:
这样一个梳理,是不是对原始模式的理解,稍微会清晰一些些了。另外,我们可以参考《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 控制台提供
DynamicRulePublisher
和DynamicRuleProvider
接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。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
,具体内容如下图:
推荐配置集编号 的命名规则为
${applicationName}-${ruleType}
,例如我们这里是demo-application-flow-rule
,即demo-application
应用的流控规则。在配置内容 中,我们设置了一个针对
GET:/demo/echo
资源,每秒允许访问 5 次。每个字段的说明如下:|------------------------------------------------------------------------------------------------------------------------------------------------------------| |
html [ { "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 数据源加载而来的。如下图所示:
③ 使用浏览器,快速访问 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
,具体内容如下图:
推荐配置项的 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 数据源加载而来的。如下图所示:
③ 使用浏览器,快速访问 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 数据源加载而来的。如下图所示:
③ 使用浏览器,快速访问 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
资源新建一条流控规则,如下图所示:
新建完成后,查看 ${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 暂时未提供高可用方案,这个上到生产肯定是有蛮大风险的。感兴趣的胖友,可以先阅读如下文章:
Sentinel 的东西真是多呀,所以胖友一定要多看看《Sentinel 官方文档》。本文的示例,大体覆盖到 Sentinel 文档所有的内容,遗漏的章节如下:
Sentinel 支持的数据源比较多,艿艿暂时只写了三个主流的,剩余的胖友可以看看如下示例代码:
- sentinel-demo-zookeeper-datasource
- sentinel-demo-etcd-datasource
- sentinel-datasource-redis 暂无示例。
- sentinel-datasource-consul 暂无示例。
- sentinel-datasource-spring-cloud-config 暂无示例。
来源:https://blog.csdn.net/weixin_42073629/article/details/106445022