微服务架构 | 5.2 基于 Sentinel 的服务限流及熔断

2022-10-21,,,,

目录
前言
1. Sentinel 基础知识
1.1 Sentinel 的特性
1.2 Sentinel 的组成
1.3 Sentinel 控制台上的 9 个功能
1.4 Sentinel 工作原理
1.5 Sentinel 源码分析
2. 安装并运行 Sentinel 控制台
2.1 安装包安装 Sentinel 控制台
2.1.1 下载 Sentinel
2.1.2 使用命令启动 Sentinel 控制台
2.1.3 访问 Sentinel 控制台
2.2 源码部署 Sentinel 控制台
2.2.1 拉取源码
2.2.2 启动 Sentinel 控制台
3. Spring Cloud Nacos 集成 Sentinel
3.1 引入 pom.xml 依赖文件
3.2 修改 bootstrap.yml 配置文件
3.3 编写业务类
3.4 基于 Sentinel 控制台添加容灾规则
3.4.1 流控规则页面说明
3.4.2 熔断规则页面说明
3.4.3 热点规则页面说明
1. 简单示例
2. 参数例外项配置
3.4.4 系统规则页面说明
3.4.5 授权规则页面说明
4. 使用 @SentinelResource 自定义限流处理逻辑
4.1 注解参数属性说明
4.2 配置与代码的关系
4.3 自定义限流处理逻辑
4.3.1 创建 CustomerBlockHandler 类用于自定义限流处理逻辑
4.3.2 在 controller 接口上配置自定义逻辑
4.3.3 在 Sentinel 控制台上配置
5. 手动配置流控规则
5.1 controller 接口
5.2 实现 InitFunc 接口
6. Sentinel 规则持久化
6.1 添加 pom.xml 依赖文件
6.2 修改 bootstrap.yml 配置文件
6.3 在 Nacos 服务器上添加配置
6.4 一些说明
7. Sentinel 控制台集成 Nacos 实现规则同步
7.1 修改依赖与配置文件
7.2 新建 Nacos 规则包,里面存放与同步的配置类
7.2.1 新建 NacosPropertiesConfiguration 类
7.2.2 新建 NacosConfiguration 类
7.2.3 新建 NacosConstants 类
7.2.4 新建 FlowRuleNacosProvider 类
7.2.5 新建 FlowRuleNacosPublisher 类
7.3 修改 FlowControllerV2 类
7.4 修改 Nacos 客户端
8. 自定义 URL 限流异常和 URL 资源清洗
8.1 自定义 URL 限流异常
8.1.1 问题描述
8.1.2 实现 UrlBlockHandler 并且重写 blocked() 方法
8.2 URL 资源清洗
8.2.1 问题描述
8.2.2 实现 UrICleaner 并重写 clean() 方法
最后


前言

参考资料

《Spring Microservices in Action》

《Spring Cloud Alibaba 微服务原理与实战》

《B站 尚硅谷 SpringCloud 框架开发教程 周阳》

《Sentinel GitHub 官网》

《Sentinel 官网》

Sentinel 是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、服务降级、系统负载保护等多个维度来帮助我们保障微服务的稳定性;


1. Sentinel 基础知识

1.1 Sentinel 的特性

丰富的应用场景:几乎涵盖所有的应用场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制等;
实时监控:开发者可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群汇总运行情况;
开源生态支持:Sentine提供开箱即用的与其他开源框架/库的整合,例如与Spring Cloud、Dubbo、gRPC的整合;
SPI 扩展点支持:提供了 SPI 扩展点支持,开发者可以通过扩展点来定制化限流规则,动态数据源适配等需求;

1.2 Sentinel 的组成

分两部分:

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

1.3 Sentinel 控制台上的 9 个功能

功能 说明
实时监控 实时监控每个资源名(接口、请求路径)的通过 QPS、拒绝 QPS 和响应时间;
簇点链路 通过树状视图和列表视图展示接口调用的关系以及通过 QPS、拒绝 QPS、并发数、平均 RT、分钟通过和拒绝等信息;
流控规则 又称:流量控制(flow control)。其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性;
熔断规则 对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩;
热点规则 又称:热点参数限流规则。热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制;
系统规则 系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性;
授权规则 根据调用来源来判断该次请求是否允许放行;
集群流控 集群流控可以解决流量不均匀导致总体限流效果不佳的问题;
机器列表 收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线;

1.4 Sentinel 工作原理

Sentinel 的核心分为三部分:工作流程、数据结构和限流算法;

调用链路是 Sentinel 的工作主流程,由各个 Slot 插槽组成,将不同的 Slot 按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级、系统保护)组合在一起;
Sentinel 中各个 Slot 承担了不同的职责,如:LogSlot 负责记录日志、StatisticSlot 负责统计指标数据、FlowSlot 负责限流等。这是一种职责分离的设计,每个模块更聚焦于实现某个功能;
在 Sentinel 中,所有的资源都对应一个资源名称(resourceName),每次访问该资源都会创建一个 Entry 对象,在创建 Entry 的同时,会创建一系列功能槽(Slot Chain),这些槽会组成一个责任链,每个槽负责不同的职责;

Slot 插槽 说明
NodeSelectorSlot 负责收集资源的调用路径,以树状结构存储调用栈,用于根据调用路径来限流降级;
ClusterBuilderSlot 负责创建以资源名维度统计的 ClusterNode ,以及创建每个 ClusterNode 下按调用来源 origin 划分的 StatisticNode;
LogSlot 在出现限流、熔断、系统保护时负责记录日志;
AuthoritySlot 权限控制,支持黑名单和白名单两种策略;
SystemSlot 控制总的入口流量,限制条件依次是总 QPS、总线程数、RT 阈值、操作系统当前 load1、操作系统当前 CPU 利用率;
FlowSlot 根据限流规则和各个Node中的统计数据进行限流判断;
DegradeSlot 根据熔断规则和各个Node中的统计数据进行服务降级;
StatisticSlot 统计不同维度的请求数、通过数、限流数、线程数等 runtime 信息,这些数据存储在 DefaultNode、OriginNode 和 ClusterNode 中;

1.5 Sentinel 源码分析

由于篇幅有限,该内容放在以下文章:
详情请见:微服务架构 | 5.4 Sentinel 流控、统计和熔断的源码分析

2. 安装并运行 Sentinel 控制台

Sentinel 与 Nacos 类似,有两种安装方式:使用已经编译好的安装包和源码部署;由于要对 Sentinel 源码进行分析,这里推荐源码部署;

这里选择的版本是 1.8.3;

2.1 安装包安装 Sentinel 控制台

2.1.1 下载 Sentinel

从官网下载 Sentinel:https://github.com/alibaba/Sentinel/releases;

2.1.2 使用命令启动 Sentinel 控制台

运行前要满足两个条件:有 java8 环境、8080 端口不能被占用;

win10 解决端口占用用以下三个命令即可(用管理员打开 cmd):
查看所有端口:netstat -ano 或者指定查看 8080 端口占用情况 netstat -aon|findstr 8080
根据进程查找应用程序:tasklist|findstr {上面查到的进程 PID}
关闭进程:taskkill /f /t /im {上面查到的应用程序}
到下载的 jar 包下启动 cmd 窗口,运行下面命令:
java -jar sentinel-dashboard-1.8.3.jar
如果不想杀掉 8080 程序可以之地端口号运行:java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777 -jar sentinel-dashboard-1.8.3.jar
-Dserver.port:指定 Sentinel 控制台的访问端口,默认是 8080;
-Dcsp.sentinel.dashboard.server:指定 Sentinel Dashboard 控制台的 IP 地址和端口,这里进行设置的目的是把自己的限流数据暴露到监控平台;
-Dproject.name:设置项目名称;

2.1.3 访问 Sentinel 控制台

发送请求:http://localhost:8080;
1.6.0 版本后引入登录功能,默认登录账号密码均为 sentinel

2.2 源码部署 Sentinel 控制台

2.2.1 拉取源码

访问 Sentinel 官方 GitHub 地址:https://github.com/alibaba/Sentinel;
先派生,再拉取代码:

2.2.2 启动 Sentinel 控制台

mvn install 将项目安装到本地;
接着找到 sentinel-dashboard 模块下的主程序类 DashboardApplication 运行即可;
*为了防止端口占用,这里笔者将 Sentinel 控制台的端口号改为了 28080;

3. Spring Cloud Nacos 集成 Sentinel

3.1 引入 pom.xml 依赖文件

<!-- Sentinel 核心包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

3.2 修改 bootstrap.yml 配置文件

spring:
application:
name: nacos-config-client #必须,构成 Nacos 配置管理 Data ID 字段的一部分
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos 服务注册中心地址
config:
server-addr: localhost:8848 #Nacos 作为配置中心地址
file-extension: yaml #指定 yaml 格式的配置
#以下新增
sentinel:
transport:
dashboard: localhost:28080 #配置Sentinel dashboard地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口

3.3 编写业务类

编写个接口测试用;
基于 Sentinel 控制台的流控规则不需要添加任何资源埋点;
在默认情况下 Sentinel Starter 会对所有 HTTP 请求进行限流;

@RestController
public class ConfigClientController {
@GetMapping("/easytest")
public String testMothod(){
return "test";
}
}

这时启动程序,访问 Sentinel 控制台,并不能看到效果。由于 Sentinel 使用的是懒加载机制,需要进行一次接口调用才能看到监控效果界面:

3.4 基于 Sentinel 控制台添加容灾规则

手动配置容灾规则请见本篇《5. 手动配置流控规则》;

3.4.1 流控规则页面说明

下图表示 1 秒钟内查询 1 次就是 OK,若超过次数 1,就直接-快速失败,报默认错误;

资源名 resource:唯一名称,默认请求路径;
针对来源 limitApp:Sentinel 可以针对调用者进行限流,填写微服务名,默认 default(不区分来源);
阈值类型 grade、单机阈值 count
QPS grade值1(每秒钟的请求数量):当调用该 API 的 QPS 达到阈值的时候,进行限流;
线程数 grade值0:当调用该 API 的线程数达到阈值的时候,进行限流;
是否集群 clusterMode:不需要集群;
流控模式
直接:API 达到限流条件时,直接限流;
关联 strategy:当关联的资源 B 达到阈值时,就限流自己 A(这里需要手速快或使用 Postman 模拟高并发);
链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)(API 级别的针对来源);
流控效果 controlBehavior
快速失败 RuleConstant.CONTROL_BEHAVIOR_DEFAULT:直接失败,抛异常 Blocked by Sentinel (flow limiting)
Warm Up(预热) RuleConstant.CONTROL_BEHAVIOR_WARM_UP:根据 codeFactor(冷加载因子,默认3)的值,阈值除以 codeFactor 得到预热时长。经过预热时长,才达到设置的 QPS 阈值。
用于秒杀系统在开启的瞬间,刚开始不行,后续慢慢OK;
排队等待 RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:匀速排队,让请求以匀速的速度通过(漏桶算法),阈值类型必须设置为 QPS,否则无效;
用于处理间隔性突发的流量;

3.4.2 熔断规则页面说明

下图表示当我们的请求响应超过 1000ms ,并且该统计的请求比例超过 50% 时(统计的请求数量需要大于 5),触发熔断;

经过熔断时长 5s 后进入探测恢复状态,若下一个请求响应时间小于 1000ms,则熔断结束;反之再次熔断;

资源名 resource:唯一名称,默认请求路径;
熔断策略 grade
慢调用比例 (SLOW_REQUEST_RATIO):请求的响应时间大于 RT 统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断;
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%;
异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断;
最大 RT、比例阈值 count:慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例和异常数模式下为对应的阈值;
慢调用比例阈值 slowRatioThreshold:仅慢调用比例模式有效(1.8.0 引入);
熔断时长 timeWindow:单位为 s;
最小请求数 minRequestAmount:请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入);
统计时长 statIntervalMs:如 60*1000 代表分钟级(1.8.0 引入);

3.4.3 热点规则页面说明

常用于:统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制;
热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效;
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式;

1. 简单示例

编写业务类
@SentinelResource 的用法详情请见本篇第 4 点;

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandlerTestHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "testHotKey";
}
public String dealHandlerTestHotKey(String p1, String p2, BlockException exception) {
return "dealHandler_testHotKey";
}

在 Sentinel 控制台上配置规则

下图表示第一个参数(索引为0)有值的话(对应上述代码的 p1),1 秒的 QPS 为 1,超过就限流,限流后调用 dealHandler_testHotKey 支持方法;

资源名 resource:唯一名称,默认请求路径;

限流模式 grade:限流模式只支持 QPS 模式;

参数索引 paramIdx:必填,对应 SphU.entry(xxx, args) 中的参数索引位置;

单机阈值 count:限流阈值,必填;

统计窗口时长 durationInSec:单位为秒,1.6.0 版本开始支持;

是否集群 clusterMode:是否是集群参数流控规则;

集群流控相关配置 clusterConfig

参数例外项 paramFlowItemList:可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型;

流控效果 controlBehavior:流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持;

测试访问

http://localhost:18082/testHotKey?p1=22:触发限流;
http://localhost:18082/testHotKey?p1=22&p2=33:触发限流;
http://localhost:18082/testHotKey?p2=33:不触发限流;

2. 参数例外项配置

当 p1 的值等于 5 时,它的阈值可以达到 200;

注意点击“添加”;
热点参数必须是基本类型或者 String;

3.4.4 系统规则页面说明

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性;

load1 表示系统的负载;
系统规则支持一下五种模式:
Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏;
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒;
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护;
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护;

3.4.5 授权规则页面说明

根据调用来源来判断该次请求是否允许放行;

资源名 resource:资源名,即限流规则的作用对象;
流控应用 limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB;
授权类型 strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式;

4. 使用 @SentinelResource 自定义限流处理逻辑

Sentinel starter 在默认情况下会为所有的 HTTP 服务提供限流埋点,所以如果只想对 HTTP 服务进行限流,那么只需要添加依赖即可,不需要修改任何代码;
如果想要对特定的方法进行限流或者降级,则需要通过 @SentinalResouce 注解来实现限流资源的定义;

4.1 注解参数属性说明

@SentinelResource 注解参数属性说明:

public @interface SentinelResource {
//资源名称,必需项(不能为空)
String value() default ""; //entry 类型,有 IN 和 OUT 两个选项,(默认为 EntryType.OUT)
EntryType entryType() default EntryType.OUT; int resourceType() default 0; //对应处理 BlockException 的函数名称,可选项
String blockHandler() default ""; /**
* blockHandler 函数默认需要和原方法在同一个类中,如果希望使用其他类的函数,则需要指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
Class<?>[] blockHandlerClass() default {}; /**
* fallback 函数默认需要和原方法在同一个类中;
* 若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象;
* 对应的函数必需为 static 函数,否则无法解析;
**/
String fallback() default ""; /**
* 默认的 fallback 函数名称,可选项,通常用于通用的 fallback逻辑(即可以用于很多服务或方法);
* 默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理;
* 若同时配置了 fallback 和 defaultFallback,则只有 fallback会生效;
* defaultFallback 函数签名要求:返回值类型必须与原函数返回值类型一致;方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;
* defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析;
**/
String defaultFallback() default ""; /**
* fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑;
* fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理;
* fallback 函数签名和位置要求:返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常
**/
Class<?>[] fallbackClass() default {}; //用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class}; Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

@SentinelResource:处理的是 Sentinel 控制台配置的违规情况,有 blockHandler 方法配置的兜底处理;
RuntimeException:如:int age = 10/0。这个是 java 运行时报出的运行时异常 RunTimeException,@SentinelResource 不管;
@SentinelResource 主管配置出错,运行出错走异常处理程序;

4.2 配置与代码的关系

4.3 自定义限流处理逻辑

4.3.1 创建 CustomerBlockHandler 类用于自定义限流处理逻辑

在 handler 包下新建 CustomerBlockHandler 类;

public class CustomerBlockHandler{
public static CommonResult handlerException(BlockException exception){
return new CommonResult(4444,"按客戶自定义,global handlerException----1");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(4444,"按客戶自定义,global handlerException----2");
}
}

4.3.2 在 controller 接口上配置自定义逻辑

@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200,"按客户自定义限流处理逻辑");
}

在实际生产中,上述所有的代码都要用 try-catch-finally 方式进行处理;

4.3.3 在 Sentinel 控制台上配置

5. 手动配置流控规则

我们除了能在 Sentinel 控制台上配置流控规则外,还可以借助 Sentinel 的 InitFunc SPI 扩展接口来实现:

需要实现自己的 InitFunc 接口;
并在 init 方法中编写规则加载的逻辑;
我们接着 3.4.3 的 controller 示例:

5.1 controller 接口

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandlerTestHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "testHotKey";
}
public String dealHandlerTestHotKey(String p1, String p2, BlockException exception) {
return "dealHandler_testHotKey";
}

5.2 实现 InitFunc 接口

public class FlowRuleInitFunc implements InitFunc{
@Override
public void init() throws Exception{
List<FlowRule>rules=new ArrayList<>();
FlowRule rule=new FlowRule();
rule.setcount(1);
rule.setResource("testHotKey");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}

SPI 是扩展点机制,如果需要被 Sentinel 加载,那么还要在 resource 目录下创建 META-INF/services/com.alibaba.csp.sentinelinit.InitFunc 文件,文件内容就是自定义扩展点 FlowRuleInitFunc 的全路径;

6. Sentinel 规则持久化

一旦我们重启应用,Sentinel 规则将消失,生产环境需要将配置规则进行持久化;
将限流配置规则持久化进 Nacos 保存,只要刷新 REST 地址,Sentinel控制台的流控规则就能看到,只要 Nacos 里面的配置不删除,针对 Sentinel 上的流控规则持续有效;

6.1 添加 pom.xml 依赖文件

<!-- nacos 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Sentinel 持久化相关 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

6.2 修改 bootstrap.yml 配置文件

spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
sentinel:
transport:
dashboard: localhost:28080
port: 8719
# 以下新增
datasource:
ds1:
nacos:
server-addr: localhost:8848 #将规则保存进 Nacos 配置中心
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json #指定配置项的内容格式,可选:JSON、XML。如果需要自定义,则可以将值配置为 custom,并配置 converter-class 指向 converte r类;
rule-type: flow #数据源中规则的类型,可选:flow、degrade、param-flow、gw-flow

6.3 在 Nacos 服务器上添加配置

/easytest 接口添加流控规则;

Resource:资源名称;
LimitApp:来源应用;
Grade:阈值类型,0表示线程数,1表示 QPS;
Count:单机阈值;
Strategy:流控模式,0表示直接,1表示关联,2表示链路;
ControlBehavior:流控效果,0表示快速失败,1表示 Warm Up,2表示排队等待;
ClusterMode:是否集群;

6.4 一些说明

访问 Sentinel 控制台可能看不见配置,多次调用 /easytest 后才能看见配置规则;
在 Sentinel 控制台上添加和修改配置不能同步到 Nacos 配置中心,服务重启后配置规则不能同步到 Nacos 上;
Nacos 在这当中扮演的角色应该是一个查询数据库,不建议在 Nacos 上修改流控规则;

7. Sentinel 控制台集成 Nacos 实现规则同步

上面说过,在 Sentinel 控制台上添加和修改配置不能同步到 Nacos 配置中心,服务重启后配置规则不能同步到 Nacos 上;
但我们可以自己实现这个功能,配置步骤如下:

7.1 修改依赖与配置文件

修改 sentinel-dashboard 模块里的 pom.xml 依赖文件;
注释掉 <scrop>:

<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!--<scope>test</scope>-->
<dependency>

修改:src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html;里的 dashboard.flowV1 为 dashboard.flow;
使之调用 FlowControllerV2 中的接口;

<li ui-sref-active="active" ng-if="!entry.isGateway">
<!--<a ui-sref="dashboard.flowV1({app: entry.app})">-->
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a>
</li>

application.yml 里添加 Nacos 服务器的配置信息:

sentinel.nacos.serverAddr=localhost:8848
sentinel.nacos.namespace=
sentinel.nacos.group-id=DEFAULT_GROUP

7.2 新建 Nacos 规则包,里面存放与同步的配置类

新建包 com/alibaba/csp/sentinel/dashboard/rule/nacos/
下面新建五个类,可以从这个目录下拷贝 src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos
新建后的图:

7.2.1 新建 NacosPropertiesConfiguration 类

在该包下新建 NacosPropertiesConfiguration.java 类,用来加载外部化配置;

@ConfigurationProperties(prefix="sentinel.nacos")
public class NacosPropertiesConfiguration {
private String serverAddr;
private String dataId;
private String groupId = "DEFAULT_GROUP";
private String namespace;
//这里省略 get/set 方法
}

7.2.2 新建 NacosConfiguration 类

在该包下创建一个 Nacos 配置类 NacosConfiguration

@EnableConfigurationProperties(NacosPropertiesConfiguration.class)
@Configuration
public class NacosConfiguration { //Converter 转换器,将 FlowRuleEntity 转化成 FlowRule,以及反向转化
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder(){
return JSON::toJSONString;
} @Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder(){
return s -> JSON.parseArray(s, FlowRuleEntity.class);
} //注入 Nacos 服务 ConfigService
@Bean
public ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws NacosException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
return ConfigFactory.createConfigService(properties);
}
}

7.2.3 新建 NacosConstants 类

在该包下创建一个 Nacos 常量类 NacosConstants。分别表示默认的 GROUP_ID 和 DATA_ID 的后缀:

public class NacosConstants {
public static final String DATA_ID_POSTFIX = "-sentinel-flow";
public static final String GROUP_ID = "DEFAULT_GROUP";
}

7.2.4 新建 FlowRuleNacosProvider 类

在该包下新建 FlowRuleNacosProvider 类实现动态从 Nacos 配置中心获取流控规则;

@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> { private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosProvider.class); @Autowired
private NacosPropertiesConfiguration nacosConfigProperties; @Autowired
private ConfigService configService; @Autowired
private Converter<String, List<FlowRuleEntity>> converter; @Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String dataID = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
//通过ConfigServic.getConfig方法从Nacos Config Server中读取指定配置信息,并通过converter转化为FlowRule规则
String rules = configService.getConfig(dataID, nacosConfigProperties.getGroupId(), 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}

7.2.5 新建 FlowRuleNacosPublisher 类

在该包下新建 FlowRuleNacosPublisher(流控规则发布类) 类,在 Sentinel 控制台 上修改完配置之后,需要调用该发布方法将数据持久化到 Nacos 中;

@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { @Autowired
private NacosPropertiesConfiguration nacosPropertiesConfiguration; @Autowired
private ConfigService configService; @Autowired
private Converter<List<FlowRuleEntity>, String> converter; @Override
public void publish(String appName, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(appName, "appName cannot be empty");
if (rules == null) {
return;
}
String dataID = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
configService.publishConfig(dataID, nacosPropertiesConfiguration.getGroupId(), converter.convert(rules));
}
}

7.3 修改 FlowControllerV2 类

上面配置的两个类(FlowRuleNacosPublisher 与 FlowRuleNacosProvider)注入进来,表示规则的拉取和规则的发布统一用我们前面自定义的两个实例;

7.4 修改 Nacos 客户端

对于应用程序来说,需要改动的地方比较少,只要注意配置文件中 data-id 的命名要以 -sentinel-flow 结尾即可,因为在 Sentinel Dashboard 中我们写了一个固定的后缀;

spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
sentinel:
transport:
dashboard: localhost:28080
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
# 修改下面这条
dataId: ${spring.appliction.name}-sentinel-flow
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow

8. 自定义 URL 限流异常和 URL 资源清洗

8.1 自定义 URL 限流异常

8.1.1 问题描述

在默认情况下,URL 触发限流后会直接返回 Blocked by Sentinel (flow 1imiting)
在实际应用中,大都采用JSON格式的数据,所以如果希望修改触发限流之后的返回结果形式;

8.1.2 实现 UrlBlockHandler 并且重写 blocked() 方法

可以通过自定义限流异常来处理,实现 UrlBlockHandler 并且重写 blocked 方法;

@Service public class CustomurlBlockHandler implements Ur1BlockHandler{
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpservletResponse, BlockException e)throws IOException{
httpServletResponse.setHeader("Content-Type","application/json;charset=UTF-8");
String message="{\"code\":999,\"msg\":\"访问人数过多\"}";
httpServletResponse.getWriter().write(message);
}

如果触发限流之后,我们希望直接跳转到一个降级页面,可以通过下面这个配置来实现:spring.cloud.sentinel.servlet.block-page-{url}

8.2 URL 资源清洗

8.2.1 问题描述

默认情况下 Sentinel 会把所有的 URL 当作资源来进行流控;
比如一个 URL /clean/{id} 带有一个参数 id,这个参数有很多种属性 {id可取整数},有多少个请求 Sentinel 默认统计多个,而我们期望是一个;

8.2.2 实现 UrICleaner 并重写 clean() 方法

对于 /clean/{id} 这个 URL,我们可以统一归集到 /clean/* 资源下:

@Service
public class CustomerUrlCleaner implements UrlCleaner{
@Override
public String clean(String originurl){
if(Stringutils.isEmpty(originur1)){
return originUrl;
}
if(originUr1.startswith("/clean/")){
return"/clean/*";
}
return originUrl;
}

最后

新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

微服务架构 | 5.2 基于 Sentinel 的服务限流及熔断的相关教程结束。

《微服务架构 | 5.2 基于 Sentinel 的服务限流及熔断.doc》

下载本文的Word格式文档,以方便收藏与打印。