Sentinel-Dashboard持久化生产环境解决方案
# 背景
Sentinel作为目前市面上常用的限流/降级/熔断平台,已经在诸多高并发项目上进行应用。通常来说一个微服务架构下的项目,流量控制、熔断降级等系统保护功能是必备的。由于现在公司都流行采用开源和商业化双线进行,Sentinel-DashBoard开源版本并不是一个生产环境拿来就能用的产品。
其主要的问题在于
- 开源版本
Sentinel-Dashboard上面的一切操作都是基于内存的,开发者配置的所有策略在客户端应用重启后都将丢失。 - 同时由于内存版本是针对某个
ip+app进行的参数设置,在k8s环境下,应用重启后ip会产生变化,导致策略失效,这些情况是完全无法接受的
Sentinel开源版本给出了Sentinel-datasource、流控规则持久化demo代码等等,他的完全体MSE Sentinel 控制台 (opens new window)拿来在云上作为企业版面向客户了
所以想要在生产环境使用Sentinel-Dashboard,规则持久化是必备的一步。现有的网络教程绝大多数只会教你改后端代码,在本文中你能够找到完整的Sentinel-Dashboard前后端是如何进行改造的,流控和熔断降级规则是怎么持久化的,簇点链路应该怎么和持久化操作一致。
# 规则持久化
根据官方推荐的改造步骤 (opens new window),我们能够大概有个初步了解,选用推荐的Push模式,而不是Pull和基于内存的原始模式
本文的教程基于Zookeeper进行持久化

从图中可以看出来Sentinel Dashboard推送配置到配置中心,之后配置中心通知各个接入Sentinel的客户端(由sentinel-datasource实现监听),完成动态配置的功能
由于Sentinel Dashboard和接入Sentinel客户端的应用没有直接关系,所以无论Sentinel Dashboard应用是否存活,你的流控熔断降级规则都能够生效(只要客户端和配置中心的通信正常)
因为当一个请求到达客户端应用时,客户端只与内存中持久化的规则进行交互,而持久化的规则是在应用初始化时从配置中心获取的
# 步骤1: 从github拉取Sentinel源码
从github对应的release (opens new window)页面下载
本文使用的是Sentinel1.8.5,从页面中下载对应的源码

解压源码并用idea打开

由于源码每个都是一个jar包,为了适配DockerFile的打包方式(根据公司内不同CICD规则进行选择),我们单独将sentinel-dashboard模块抽离出来,他本身是一个SpringBoot项目
# 步骤2: 接入必备依赖
在sentinel-dashboard的pom文件中增加必备的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring.cloud.alibaba.version}</version>
</dependency>
2
3
4
5
2
3
4
5
其中spring.cloud.alibaba.version和sentinel1.8.5适配,为2021.0.4.0
将官方提供的zk依赖scope test移除,使得zk客户端生效

同时需要移除curator自带的zk版本与公司内的zk客户端版本适配(本文选用的是3.4.6,是目前zk硬件条件所适配的软件版本)
完整依赖如下
<!--for Zookeeper rule publisher sample-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
同时你需要升级sentinel-dashboard本身的SpringBoot版本与SpringCloud兼容

然后在application.properties中关闭版本兼容性检查

# 步骤3: 整合注册中心代码
在sentinel-dashboard的test目录中,官方提供了apollo/nacos/zookeeper3种注册中心的对于流控规则的必备改造代码

我们的注册中心是zk,所以我们将zk相关的代码拷贝到项目中main目录下rule的位置

这里的DegradeRuleZookeeperProvider和DegradeRuleZookeeperPublisher是熔断规则持久化的必备代码,官方没有提供,后续本文将会提到
# 步骤4: 更改FlowControllerV2
com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2是官方预留的流控规则持久化Controller入口
我们需要将默认的基于内存的provider和publisher注入进行更改
从Default

改为zk

这两个Bean即是步骤3中我们拷贝的代码


# 步骤5: 修改默认的zk地址
踩坑,官方的改造教程 (opens new window)以及网上的许多文章(大多数是本地启动一个zk)并没有告诉你要改这一步,我是启动后报错了才反应过来
需要从

改为配置注入

# 步骤6: 修改sidebar.html
路径为src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
将原本的流控规则dashboard.flowV1调用改为dashboard.flow调用

# 步骤7: 查看改造效果
此时点击新增流控规则,规则就会持久化到zk中了,即使客户端应用重启,规则也不会丢失

同时,页面多了一个回到单机页面的按钮
而单机页面仍然是针对某个ip的机器,基于内存的,内存规则和持久化规则能够混合使用,但不推荐

# 客户端接入Sentinel
对于应用客户端,接入sentinel必备的pom如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
<exclusions>
<exclusion>
<artifactId>zookeeper</artifactId>
<groupId>org.apache.zookeeper</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo3-adapter</artifactId>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
接入Sentinel的应用各个版本为
SpringBoot:2.7.10spring-boot-starter-actuator:由SpringBoot版本控制spring-cloud-starter-alibaba-sentinel:2021.0.4.0spring-cloud-starter-bootstrap:3.1.5sentinel-datasource-zookeeper:1.8.5sentinel-apache-dubbo3-adapter:1.8.6(因为1.8.5有bug,官方在1.8.6修复)
配置中心接入
# 服务连接时则出现在控制台
spring.cloud.sentinel.eager=true
# dashboard地址
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=你的dashboard地址
# 持久化配置,数据源1 选用zookeeper,可配置多数据源,参考https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
# 数据类型
spring.cloud.sentinel.datasource.ds1.zk.data-type=json
# 存储path,在有dataId和groupId时可不使用,dataId和groupId方便后期平滑迁移nacos
# spring.cloud.sentinel.datasource.ds1.zk.path=/sentinel_rule_config/你的应用名
# 流控规则
# groupId固定sentinel_rule_config,因为dashboard固定值
spring.cloud.sentinel.datasource.ds1.zk.groupId=sentinel_rule_config
# dataId和spring.application.name保持一致
spring.cloud.sentinel.datasource.ds1.zk.dataId=你的应用名
# zk地址
spring.cloud.sentinel.datasource.ds1.zk.server-addr=你的zk地址
# 规则类型,参考com.alibaba.cloud.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.ds1.zk.rule-type=flow
# 熔断规则
# groupId固定sentinel_rule_config,因为dashboard固定值
spring.cloud.sentinel.datasource.ds2.zk.data-type=json
spring.cloud.sentinel.datasource.ds2.zk.groupId=sentinel_rule_config
# dataId和spring.application.name保持一致
spring.cloud.sentinel.datasource.ds2.zk.dataId=你的应用名
# zk地址
spring.cloud.sentinel.datasource.ds2.zk.server-addr=你的zk地址
# 规则类型,参考com.alibaba.cloud.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.ds2.zk.rule-type=degrade
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
一个基本上的接入配置如上,这种配置直接去结合持久化改造有一定的问题,本文后文将会给出改造后的接入配置
# 跟着官网教程改造后仍然存在的问题
- 新增流控规则的确持久化了,但从簇点链路直接对资源进行流控,仍然是走的内存
- 由于簇点链路中的接口新增规则后是内存处理,所以在簇点链路新增的规则要点击回到单机页面后才能看得到,而持久化的规则需要在流控规则页面点击才行
2个页面各管各的,使用起来不流畅,但其实有了持久化规则后,我们就不再需要内存页面了
# 完全Sentinel-Dashboard高阶改造
我们先暂时不管以上2个问题,因为他还关系到另外的坑点,我们先从熔断降级规则持久化开始继续改造
# 步骤8: 熔断降级规则持久化-后端改造
官网的教程只交了如何让流控规则持久化,网上只有极少部分教程做了CV式的熔断降级规则的持久化
依赖少部分教程+观察流控规则持久化代码,我们可以整理出必备改造方式
- 在
src/main/java/com/alibaba/csp/sentinel/dashboard/rule/zookeeper路径复制FlowRuleZookeeperPublisher和FlowRuleZookeeperProvider类,复制之后改名为DegradeRuleZookeeperProvider和DegradeRuleZookeeperPublisher - 修改对应的
Component名称为degradeRuleZookeeperProvider和degradeRuleZookeeperPublisher


- 修改实现接口、
Converter、Provider和Publisher中方法的泛型为熔断降级实体DegradeRuleEntity


- 在
ZookeeperConfig中增加熔断降级Converter实现
Converter实现参考flow规则

- 拷贝
DegradeController并改名为DegradeControllerV2进行基础修改
更改requestMapping路径为/v2/degrade,更改ruleProvider和rulePublisher的Bean为Degrade对应的Bean

- 拷贝
FlowControllerV2的/rules路由实现到DegradeControllerV2,并进行改造
拷贝FlowControllerV2中的

改造后的DegradeControllerV2

这里的区别点在于filter规则,过滤掉那些不属于熔断降级规则的规则,通过TimeWindow判断
- 参考
FlowControllerV2改造DegradeControllerV2持久化方法
找到DegradeControllerV2中原始的publishRules方法
从内存操作

改造到zk操作

- 参考
FlowControllerV2改造DegradeControllerV2剩余路由方法,调用publishRules进行持久化



- 删除无用的
rules.json路由

到这里,熔断降级规则持久化-后端代码基础部分就改造完成了
# 步骤9: 熔断降级规则持久化-前端代码改造
大家只是说熔断降级规则持久化和流控规则持久化类似
但完全没有提及官网的教程是直接把流控规则持久化的前端代码写好了的,而熔断降级规则的持久化前端代码是完全没有的
同时在直接CV式完成熔断规则持久化后,又出现了非常多的踩坑点,接下来的踩坑直接进入了无网络教程的区域,需要依靠前端知识+阅读sentinel-dashboard源码来完成
首先我们回到src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
找到熔断规则的html,他是调用的dashboard.degrade的js方法

中键跟踪degrade方法,发现有2个,一个在scripts一个在dist

根据nodejs的知识,我们可以确定原始方法在scripts中,而dist是nodejs打包的无缩进全量js产物
进入scripts/app.js中查看对应方法

由于熔断降级规则前端代码没有V2的版本,所以我们需要新增degrade_v2.html,degrade_v2.js
注意下文修改的文件均在/scripts目录下,非dist目录下同名方法
按如下步骤
- 拷贝
src/main/webapp/resources/app/views/degrade.html到同级目录,改名为degrade_v2.html
html文件暂不需要修改
- 拷贝
src/main/webapp/resources/app/scripts/controllers/degrade.js到同级目录,改名为degrade_v2.js
浏览该js文件我们可以大概知道degrade.js用于动态生成弹框

在degrade.js中有一个入参叫DegradeService,跟踪进去发现,DegradeService内部写有CRUD操作真正调用的后端Controller路由路径


- 修改
app.js中原始degrade调用方法到v2

这里给前端controller改了个名字叫DegradeControllerV2,并不是后端的Controller
- 修改
degrade_v2.js中controller名称

- 进入
degrade_service.js,全量修改DegradeService调用路由路径为v2

这里调用rules而不是走内存的rules.json
build前端项目,生成构建产物,重新生成dist下的app.js
在上文中修改了前端代码并不能直接生效,因为前端调用的始终是dist目录下的app.js代码
此时有2种选择
重新打包前端项目
手动在没有缩进符的
dist/app.js下进行上述的同样操作(不推荐)
从文件中我们可以看出,前端项目是nodejs项目,且基于gulp进行自动化打包

你的环境需要安装gulp(3.9.1)和nodejs(v10.24.0)版本,高版本将无法编译

nvm是nodejs的管理软件,需要通过安装包安装,对应NVM链接 (opens new window)


在package.json同级目录输入
npm install --registry=https://registry.npm.taobao.org
安装必备依赖
安装完成之后点击package.json内的build

build将执行总任务,生成必备的dist产物


到这里,熔断降级规则持久化-前端改造就已完成
# 步骤10: 流控和熔断降级规则在默认sentinel-dashboard配置下的覆盖冲突、多出列表数据
前面几个步骤完成了熔断降级规则持久化+流控规则持久化,但正当你觉得大功告成的时候
在控制台新增N条流控规则后,再新增1条熔断规则,会发现流控规则全部没有了,只剩你新增的熔断规则
此时熔断规则覆盖了流控规则,这时候你可能怀疑是之前修改出的bug,但其实不是,配置隔离也是改造的必备点,只是官网没有提及
如果你改造DegradeController时漏掉了步骤8的第6步,那么还将会出现流控/熔断规则列表中同时出现流控和熔断规则
根据F12查看dashboard调用的api,我们可以知道前端新增流控或熔断降级规则调用的路由路径均为/v2/xxx/rule
获取流控或熔断降级规则调用的路由路径均为/v2/xxx/rules
我们阅读FlowControllerV2和DegradeControllerV2的代码可以知道,最终存储zk的步骤由FlowRuleZookeeperPublisher和DegradeRuleZookerPublisher的publish方法提供
从zk获取规则的步骤由FlowRuleZookeeperPublisher和DegradeRuleZookerPublisher的getRules方法提供
其中DegradeRuleZookerPublisher代码是从FlowRuleZookeeperPublisher拷贝过来的,所以我们直接看FlowRuleZookeeperPublisher的代码即可

这段代码逻辑很简单,根据app名称获取zk的存储路径,如果没有就新建这个路径,之后把新增的rule转化为对应规则实体,存储到zk中
看到这里你应该能够反应过来,Flow和Degrade存储的是同一个路径,而且setData是直接覆盖的,没有增量写的操作,这就是为什么会发生覆盖的原因
同时需要注意,上文代码中的ZookeeperConfigUtil中默认存储根路径为/sentinel_rule_config

我们的改造只需要隔离2个配置的存储路径即可
# 步骤11: 隔离流控和熔断降级规则持久化路径
- 在
FlowRuleZookeeperPublisher增加流控规则固定存储路径key,规则为:默认根路径/app名称/flow_rule

- 在
DegradeRuleZookerPublisher增加熔断降级规则固定存储路径key,规则为:默认根路径/app名称/degrade_rule

同理获取zk规则的路径也需要进行变更
- 在
FlowRuleZookeeperProvider增加流控规则固定存储路径key,保证列表查询接口和持久化接口获取数据路径一致

- 在
DegradeRuleZookeeperProvider增加熔断降级规则固定存储路径key,保证列表查询接口和持久化接口获取数据路径一致

- 客户端应用配置修改
由于Dashboard推送持久化规则的位置发生变更,你的客户端配置也需要进行变更
因为接入Sentinel的应用,默认拉取zk的路径是是groupId+dataId,完整配置如下
# 服务连接时则出现在控制台
spring.cloud.sentinel.eager=true
# dashboard地址
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=你的dashboard地址
# 持久化配置,数据源1 选用zookeeper,可配置多数据源,参考https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
# 数据类型
spring.cloud.sentinel.datasource.ds1.zk.data-type=json
# 存储path,在有dataId和groupId时可不使用,dataId和groupId方便后期平滑迁移nacos
# spring.cloud.sentinel.datasource.ds1.zk.path=/sentinel_rule_config/你的应用名
# 流控规则
# groupId固定sentinel_rule_config,因为dashboard固定值
spring.cloud.sentinel.datasource.ds1.zk.groupId=sentinel_rule_config
# dataId和spring.application.name保持一致
spring.cloud.sentinel.datasource.ds1.zk.dataId=你的应用名/flow_rule
# zk地址
spring.cloud.sentinel.datasource.ds1.zk.server-addr=你的zk地址
# 规则类型,参考com.alibaba.cloud.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.ds1.zk.rule-type=flow
# 熔断规则
# groupId固定sentinel_rule_config,因为dashboard固定值
spring.cloud.sentinel.datasource.ds2.zk.data-type=json
spring.cloud.sentinel.datasource.ds2.zk.groupId=sentinel_rule_config
# dataId和spring.application.name保持一致
spring.cloud.sentinel.datasource.ds2.zk.dataId=你的应用名/degrade_rule
# zk地址
spring.cloud.sentinel.datasource.ds2.zk.server-addr=你的zk地址
# 规则类型,参考com.alibaba.cloud.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.ds2.zk.rule-type=degrade
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
注意替换配置,同时注意rule-type,不同规则需要配置不同的rule-type,以后如果新增其他规则,配置中心也需要新增对应配置,其中groupId为dashboard固定值,dataId需要增加/flow_rule或/degrade_rule,因为上文改造的配置隔离

如果zk中有熔断配置,但配置中心只配置了流控规则,那么熔断规则将对应用不起作用(因为没配置,所以找不到从哪里获取熔断规则)
# 步骤12: 改造簇点链路新增规则-由内存变为持久化到zk
上述步骤已经解决了大部分问题,流控规则和熔断规则在各自的列表上体验正常了
但从簇点链路上点击流控或熔断仍然是走的内存的,点击这里添加后跳转的页面也是内存列表的页面,不是持久化的页面
所以我们需要改造簇点链路的前端代码,让他新增时走持久化方法,跳转也改到持久化页面

- 找到src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html的簇点链路代码块

跟踪进identity方法

进入Controller的实现identity.js
- 改变
identity.js所有关于流控和降级规则的路由到V2,使得新增规则后跳转持久化页面
从左至右


- 改变
identity.js中原始Service到V2
使得新增方法采用持久化Service而不是单机
从

改变为

之所以这里DegradeService没有变动,是因为我们直接在DegradeService进行的修改,且没有改动名称,如果你改动了名称那么也需要同步修改
- 替换
identity.js中所有FlowService和DegradeService
替换service引用使他们调用时采用持久化方案
在idea中ctrl+F,之后ctrl+R,开启严格大小写和word匹配,替换的地方有4处

本文的DegradeService无需替换
这样就完成了簇点链路的新增规则持久化改造
# 步骤13: 隐藏流控和熔断降级规则单机页面
由于有了持久化之后,内存的规则没有必要保留了,我们需要移除流控规则列表和熔断规则列表的回到单机页面的按钮
对于flow_v2.html,改造如下,从左至右

对于degrade_v2.html,改造如下,从左至右

对于identity.html,改造如下,从左至右
簇点链路列表移除了机器维度的显示,因为持久化的规则不再通过ip进行区分,是通过app名称进行控制,所以机器维度的显示没有必要

# 最终效果
经过上述诸多步骤的改造,一个可以基本在生产环境使用的Sentinel-Dashboard就完成了,同时依靠本文教程,对于剩余部分规则的改造相信也能够变得更加容易
最终效果如下


# 部分源码解析
# 客户端监听zk变更
通过改造过程我们可以知道,dashboard会push流控和熔断规则到zk,但客户端怎么知道zk数据变更了呢?
我们在项目默认引入了sentinel-datasource-zookeeper,这个包中只有一个类

该DataSource初始化时会调用init方法,init方法中有initZookeeperListener

在Listener中有一个curator提供的监听方法,他订阅了持久化的zk地址,当地址中path路径数据发生变更时,就会进行规则刷新

# Sentinel如何适配的Dubbo3
在项目中默认引入了sentinel-apache-dubbo3-adapter1.8.6的版本
里面内置了2个Filter文件,包含3个Filter定义

分别是
sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo3.SentinelDubboProviderFilter
sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo3.SentinelDubboConsumerFilter
dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo3.DubboAppContextFilter
2
3
2
3
大概看一下SentinelDubboProviderFilter,也是实现的Dubbo的Filter实现的SPI

对应的实现和编码形式写Sentinel资源类似,利用了Sentinel提供的api

需要注意的是,这里使用的sentinel-apache-dubbo3-adapter1.8.6的版本,而不是1.8.5,官方在1.8.5才支持dubbo3的adapter,在1.8.5中居然是这么写的

而该实现类对应的包名却是

这直接导致了引入了dubbo3适配包,但这三个Filter却不生效
这种低级错误,官方在发布了1.8.5版本2个月后,在1.8.6版本才修复

- 01
- SpringCache基本配置类05-16
- 03
- Rpamis-security-原理解析12-13
