Sentinel Dashboard(基于1.8.1)流控规则持久化到Nacos——涉及部分Sentinel Dashboard源码改造
前言
之前虽然也一直在使用sentinel实现限流熔断功能,但却没有好好整理之前看的源码与资料,今天有时间将之前自己整理过的资料写成一篇博文,或者是是一篇关于Sentinel(基于目前最近版本1.8,如果没有特殊说明,都指最新1.8版本)持久化Nacos的指南,因为我发现网上的一些博文虽然有参考价值但却没有好好完善好细节,一知半解,或者版本比较老不具备参考价值。比如说为什么要做这一步,这一步需要完成什么具体工作等等。所以尽我所能,详细介绍下手把手整合Sentinel与Nacos,实现Sentinel Dashboard控制台到Nacos配置中心的流控规则通信并下发规则到具体应用。
前提是要对Sentinel Dashboard跟Nacos有一定的了解,具体可以查看官方wiki,参考资料也是来源于此,再加上对sentinel-dashboard源码参考改造。
一、准备工作
1、Sentinel Dashboard持久化
我们首先需要知道:在Sentinel Dashboard中配置规则之后重启应用就会丢失,所以实际生产环境中需要配置规则的持久化实现,Sentinel提供多种不同的数据源来持久化规则配置,包括file,redis、nacos、zk。
这就需要涉及到Sentinel Dashboard的规则管理及推送功能:集中管理和推送规则。sentinel-core
提供 API 和扩展接口来接收信息。开发者需要根据自己的环境,选取一个可靠的推送规则方式;同时,规则最好在控制台中集中管理。
而规则管理推送主要有以下三种模式:
(以上部分文字跟截图来源自官方wiki)
很明显,我们需要的是第三种Push模式,即Sentinel Dashboard统一管理配置(有良好的UI界面,为什么不能统一管理呢,明显比Nacos编写json要专业),然后将规则统一推送到Nacos并持久化(生成配置文件),最后客户端监听Nacos(这一部了解使用过Nacos的话应该很熟,采用ConfigService.getConfg()方法获取配置文件),下发配置生成Rule。如下图(虚线部分不推荐):
换句话说就是实现Sentinel Dashboard与Nacos之间的相互通信:
- Sentinel Dashboard界面配置流控规则---发布/推送--->Nacos生成配置文件并持久化;
- 通过Nacos配置文件修改流控规则---拉取--->Sentinel Dashboard界面显示最新的流控规则。
需要注意的是:
- 在Nacos控制台上修改流控制,虽然可以同步到Sentinel Dashboard,但是Nacos此时应该作为一个流控规则的持久化平台,所以正常操作过程应该是开发者在Sentinel Dashboard上修改流控规则后同步到Nacos,遗憾的是目前Sentinel Dashboard不支持该功能。
- 试想下,如果公司没有统一在Sentinel Dashboard或Nacos中二选一进行配置,而是一会在Sentinel Dashboard配置,一会在Nacos配置。那么就会出现很严重的问题(流控规则达不到预期,配置数据不一致),所以推荐使用Sentinel Dashboard统一界面进行配置管理流控规则
正因为Sentinel Dashboard当前版本(截至目前为止是1.8.1-SNAPSHOT)暂不支持,但是可以通过改造部分源码实现此功能,具体请看下面介绍。
2、Sentinel Dashboard流控规则源码改造须知
首先通过git拉取下载源码,导入idea工程,解析maven后观察sentinel-dashboard模块目录结构
git clone https://github.com/alibaba/Sentinel.git
github可能会很慢,如果只是研究源码了解的话,有需要源码打包的话,可以评论或私信发给你压缩包。
改造前,我们所要了解实现Sentinel Dashboard与Nacos相互通信需要经历哪些流程或者说是缺少哪些流程,我们才好对症下药,根据我的理解我归纳总结出一下几点
(1)流控规则Controller入口
Sentinel Dashboard的流控规则下的所有操作,都会调用Sentinel-Dashboard源码中的FlowControllerV1类,这个类中包含流控规则本地化的CRUD操作;
在com.alibaba.csp.sentinel.dashboard.controller.v2包下存在一个FlowControllerV2;类,这个类同样提供流控规则的CURD,与V1不同的是,它可以实现指定数据源的规则拉取和发布。
官方说明:
从 Sentinel 1.4.0 开始,我们抽取出了接口用于向远程配置中心推送规则以及拉取规则:
DynamicRuleProvider<T>
: 拉取规则DynamicRulePublisher<T>
: 推送规则以 Nacos 为例,若希望使用 Nacos 作为动态规则配置中心,用户可以提取出相关的类,然后只需在
FlowControllerV2
中指定对应的 bean 即可开启 Nacos 适配
@Autowired @Qualifier("flowRuleNacosProvider") private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; @Autowired @Qualifier("flowRuleNacosPublisher") private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
所以根据官网说明,我们知道,FlowControllerV2依赖两个非常重要的类
- DynamicRuleProvider:动态规则的拉取,从指定数据源中获取控制后在Sentinel Dashboard中展示。
- DynamicRulePublisher:动态规则发布,将在Sentinel Dashboard中修改的规则同步到指定数据源中。
只需要扩展这两个类,然后集成Nacos来实现Sentinel Dashboard规则同步。
(2)Sentinel Dashboard前端sidebar.html页面入口
在目录resources/app/scripts/directives/sidebar找到sidebar.html,里面有关于V1版本的请求入口:
<li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li>
对应的JS请求如下,可以看到请求就是V1版本的Controller,那么之后的改造需要重新对应V2版本的Controller
.state('dashboard.flowV1', { templateUrl: 'app/views/flow_v1.html', url: '/flow/:app', controller: 'FlowControllerV1', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/flow_v1.js', ] }); }] } })
(3)Sentinel Dashboard缺少Nacos配置
在源码中虽然官方提供了test示例(即test目录)下关于Nacos等持久化示例,但是具体的实现还需要一些细节,比如在Sentinel Dashboard配置Nacos的serverAddr、namespace、groupId,并且通过Nacos获取配置文件获取服务列表等。
例如:NacosConfig中ConfigFactory.createConfigService("localhost")并没有实现创建具体的nacos config service,而是默认localhost
@Configuration public class NacosConfig { @Bean public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ConfigService nacosConfigService() throws Exception { return ConfigFactory.createConfigService("localhost"); } }
application.properties文件中也没有Nacos的相关配置
#spring settings spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true #cookie name setting server.servlet.session.cookie.name=sentinel_dashboard_cookie #logging settings logging.level.org.springframework.web=INFO logging.file=C:\\Users\\Administrator/logs/csp/sentinel-dashboard.log logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n #logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n #auth settings auth.filter.exclude-urls=/,/auth/login,/auth/logout,/registry/machine,/version auth.filter.exclude-url-suffixes=htm,html,js,css,map,ico,ttf,woff,png # If auth.enabled=false, Sentinel console disable login auth.username=sentinel auth.password=sentinel # Inject the dashboard version. It's required to enable # filtering in pom.xml for this resource file. sentinel.dashboard.version=1.8.1-SNAPSHOT
(4)流控规则配置文件约束
在NacosConfigutils已经指定了默认的流控规则配置文件的groupId等,但是如果需要指定的话这里也需要修改
public final class NacosConfigUtil { /** * 流控规则配置文件默认在SENTINEL_GROUP组、DATA_ID以-flow-rules结尾 */ public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules"; public static final String GROUP_ID = "SENTINEL_GROUP"; // 省略 }
这样我们在Sentinel客户端就可以这么配置指定流控规则配置文件约束了
spring.cloud.sentinel.datasource.flow.nacos.server-addr=127.0.0.1:8848 spring.cloud.sentinel.datasource.flow.nacos.data-id=${spring.application.name}-flow-rules spring.cloud.sentinel.datasource.flow.nacos.group-id=SENTINEL_GROUP spring.cloud.sentinel.datasource.flow.nacos.data-type=json spring.cloud.sentinel.datasource.flow.nacos.rule-type=flow
二、改造Sentinel Dashboard源码实现Nacos持久化
有了以上的须知以及改造前准备工作之后,我们可以开始进行改造源码,其中需要修改部分都会做相关注释
1、在pom.xml文件中去掉test scope注释
这是因为官方提供的Nacos持久化用例都是在test目录下,所以scope需要去除test,需要sentinel-datasource-nacos包的支持。之后将修改好的源码放在源码主目录下,而不是继续在test目录下。
<!-- for Nacos rule publisher sample --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <!--<scope>test</scope>--> </dependency>
2、修改前端路由配置(sidebar.html)
找到resources/app/scripts/directives/sidebar/sidebar.html文件修改,修改flowV1为flow,去掉V1,这样的话会调用FlowControllerV2接口
<!--<li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li>--> <!-- 修改为flow,直接调用FlowControllerV2 --> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li>
这样就可以通过js跳转至FlowControllerV2了
.state('dashboard.flow', { templateUrl: 'app/views/flow_v2.html', url: '/v2/flow/:app', controller: 'FlowControllerV2', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ 'app/scripts/controllers/flow_v2.js', ] }); }] } })
3、创建nacos配置
(1)流控配置文件约束
我们采用官方的约束,即 默认 Nacos 适配的 dataId 和 groupId 约定如下:
- groupId: SENTINEL_GROUP
- 流控规则 dataId: {appName}-flow-rules,比如应用名为 appA,则 dataId 为 appA-flow-rules
所以不需要修改NacosConfigUtil.java了,但这是展示是为了步骤的完整性。
(2)创建读取nacos配置的NacosPropertiesConfiguration文件并且application.properties指定配置
package com.alibaba.csp.sentinel.dashboard.rule.nacos; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "sentinel.nacos") public class NacosPropertiesConfiguration { private String serverAddr; private String dataId; private String groupId = "SENTINEL_GROUP"; // 默认分组 private String namespace; // 省略 getter/setter }
然后配置sentinel-dashboar/resources/application.properties中配置nacos配置,以为sentinel.nacos为前缀:
# nacos config server sentinel.nacos.serverAddr=127.0.0.1:8848 sentinel.nacos.namespace= sentinel.nacos.group-id=SENTINEL-GROUP
(3)改造NacosConfig,创建NacosConfigService
@EnableConfigurationProperties(NacosPropertiesConfiguration.class) @Configuration public class NacosConfig { @Bean public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws Exception { Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr()); properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace()); return ConfigFactory.createConfigService(properties); // return ConfigFactory.createConfigService("localhost"); } }
NacosConfig主要做两件事:
1) 注入Convert转换器,将FlowRuleEntity转化成FlowRule,以及反向转化
2) 注入Nacos配置服务ConfigService
4、动态实现从Nacos配置中心获取流控规则——重写FlowRuleNacosProvider与FlowRuleNacosPublisher类
重写FlowRuleNacosProvider类
@Service("flowRuleNacosProvider") public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> { public static final Logger log = LoggerFactory.getLogger(FlowRuleNacosProvider.class); @Autowired private ConfigService configService; @Autowired private Converter<String, List<FlowRuleEntity>> converter; /** * 1)通过ConfigService的getConfig()方法从Nacos Config Server读取指定配置信息 * 2)通过转为converter转化为FlowRule规则 * @param appName * @return * @throws Exception */ @Override public List<FlowRuleEntity> getRules(String appName) throws Exception { String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, 3000); log.info("obtain flow rules from nacos config:{}", rules); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return converter.convert(rules); } }
重写FlowRuleNacosPublisher类:
@Service("flowRuleNacosPublisher") public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { public static final Logger log = LoggerFactory.getLogger(FlowRuleNacosPublisher.class); @Autowired private ConfigService configService; @Autowired private Converter<List<FlowRuleEntity>, String> converter; /** * 通过configService的publishConfig()方法将rules发布到nacos * @param app app name * @param rules list of rules to push * @throws Exception */ @Override public void publish(String app, List<FlowRuleEntity> rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } log.info("sentinel dashboard push rules: {}", rules); configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, converter.convert(rules)); } }
5、复制到源码主目录下
之后需要将上述文件(com.alibaba.csp.sentinel.dashboard.test.rule.nacos)复制到com.alibaba.csp.sentinel.dashboard.rule.nacos目录下,即去掉test目录,这样是以内源码中Nacos等持久化的配置都是在test中,打包jar的时候并不会打包进去,所以需要copy到主源码目录下。
6、修改FlowControllerV2类,使用@Qulifier将上面配置的两个类注入进来
@RestController @RequestMapping(value = "/v2/flow") public class FlowControllerV2 { private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class); @Autowired private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository; /*@Autowired @Qualifier("flowRuleDefaultProvider") private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; @Autowired @Qualifier("flowRuleDefaultPublisher") private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;*/ /** * 修改默认publisher/provider为Nacos * 使用@Qualifier指定bean */ @Autowired @Qualifier("flowRuleNacosProvider") private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; @Autowired @Qualifier("flowRuleNacosPublisher") private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher; // 省略 }
7、mvn clean package打包
先install sentinel-parent保证依赖包已经install到本地repository
之后打包sentinel-dashboard模块,执行mvn clean package命令,打包成jar包
如果以上步骤嫌麻烦,或者中间过程哪里有问题,可以私信我直接要jar包。
三、流控规则持久化测试
1、编写Sentinel客户端
(1)创建springboot应用,编写pom文件如下:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <sentinel.version>1.8.1-SNAPSHOT</sentinel.version> </properties> <dependencies> <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>${sentinel.version}</version> </dependency> <!-- 通过nacos持久化流控规则 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>${sentinel.version}</version> </dependency> <!-- sentinel AspectJ 的扩展用于自动定义资源 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>${sentinel.version}</version> </dependency> <!-- sentinel 整合spring cloud alibaba --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.1.2.RELEASE</version> </dependency> <!-- sentinel客户端与dashboard通信依赖 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>${sentinel.version}</version> </dependency> </dependencies>
(2)配置nacos,配置sentinel dashboard datasource信息:
1)bootstrap.properties中配置Nacos Config Server
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
2)application.properties配置sentinel dashboard datasource
server.port=6003 spring.application.name=sentinel management.endpoints.web.exposure.include=* spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.sentinel.transport.dashboard=127.0.0.1:6005 #指定csp.sentinel.api.port时需要配置,否则默认8719 #spring.cloud.sentinel.transport.port=6007 # sentinel nacos配置 spring.cloud.sentinel.datasource.flow.nacos.server-addr=127.0.0.1:8848 spring.cloud.sentinel.datasource.flow.nacos.data-id=${spring.application.name}-flow-rules spring.cloud.sentinel.datasource.flow.nacos.group-id=SENTINEL_GROUP spring.cloud.sentinel.datasource.flow.nacos.data-type=json spring.cloud.sentinel.datasource.flow.nacos.rule-type=flow
(3)编写SayHelloController,指定/hello资源节点
package com.cloud.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SayHelloController { @RequestMapping("/hello") public String sayHello(){ return "hello Jian"; } }
2、启动Nacos
我使用的是win10下启动nacos,之后登录Nacos
查看起初是没有${spring.application.name}-flow-rules配置文件,也没有SENTINEL-GROUP分组;
但是服务列表会sentinel客户端实例:
分配的虚拟IP与port
3、启动sentinel dashboard控制台
(1)启动sentinel dashboard
找到target/sentinel-dashboard.jar,执行命令:
java -Dserver.port=6005 -Dcsp.sentinel.dashboard.server=localhost:6006 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=6007 -jar target/sentinel-dashboard.jar
具体的启动参数介绍:
-Dserver.port=6005 控制台端口,sentinel控制台是一个spring boot程序。客户端配置文件需要填对应的配置,如:spring.cloud.sentinel.transport.dashboard=192.168.1.102:8718
-Dcsp.sentinel.dashboard.server=localhost:6007 控制台的地址,指定控制台后客户端会自动向该地址发送心跳包。
-Dproject.name=sentinel-dashboard 指定Sentinel控制台程序的名称
-Dcsp.sentinel.api.port=8719 可选项,客户端提供给Dashboard访问或者查看Sentinel的运行访问的参数,默认8719
其它启动配置项,具体查看官方wiki
(2)登录sentinel dashboard配置流控规则
1)输入localhost:6005访问sentinel dashboard控制台(登录用户/密码默认sentinel)
2)选择sentinel菜单-流控规则-新增流控规则-输入配置-新增
如图所示,我们选择QPS阈值类型,并且count为2,流控模式为默认的直接模式,流控效果快速失败(这些配置具体含义参看官网wiki)
注意:一开始可能会空白页面,这可能是由于机器时间机制导致的,此时可能还未发送心跳,加之sentinel控制台默认的又是懒加载模式(可去除该设置),所以最好是我们访问sentinel客户端的/hello接口然后刷新页面,即访问:http://192.168.1.156:6003/hello(ip:port是由Nacos分配的虚拟地址)
4、访问/hello接口
不停刷新访问/hello接口,观察sentinel dashboard界面中的实时监控。看到有通过QPS与拒绝QPS的实时监控情况,说明该sentinel客户端已成功接入sentinel dashboard。
5、测试Sentinel Dashboard流控规则到Nacos的持久化
(1)确认Sentinel Dashboard是否能正确发布流控规则到Nacos
在Sentinel Dashboard针对sentinel客户端的/hello资源节点已经配置了流控规则
此时Nacos会对此次流控规则生成持久化配置文件,切换到Nacos-配置列表查看确实存在分组SENTINEL_GROUP下的sentinel-flow-rules配置文件
点击查看具体内容,发现关键信息都是正确的,说明Sentinel Dashboard发布到Nacos通信已经打通
(2)确认Sentinel Dashboard从nacos拉取流控规则配置是否成功
修改分组SENTINEL_GROUP下的sentinel-flow-rules配置文件,修改count(QPS数)为5,然后点击发布
发布后切换到Sentinel Dashboard查看/hello资源点的流控规则的阈值是否发生变化
明显已经发生变化,因为Sentinel Dashboard是懒加载模式,所以刷新后后台才有日志输出:
2020-12-15 19:40:11.499 INFO 11196 --- [nio-6005-exec-8] c.a.c.s.d.r.nacos.FlowRuleNacosProvider : obtain flow rules from nacos config:[{"app":"sentinel","clusterConfig":{"acquireRef useStrategy":0,"clientOfflineTime":2000,"fallbackToLocalWhenFail":true,"resourceTimeout":2000,"resourceTimeoutStrategy":0,"sampleCount":10,"strategy":0,"thresholdType":0,"windowInterva lMs":1000},"clusterMode":false,"controlBehavior":0,"count":2.0,"gmtCreate":1608026073444,"gmtModified":1608026073444,"grade":1,"id":2,"ip":"169.254.102.85","limitApp":"default","port": 8720,"resource":"/hello","strategy":0}] 2020-12-15 19:51:22.612 INFO 11196 --- [nio-6005-exec-9] c.a.c.s.d.r.nacos.FlowRuleNacosProvider : obtain flow rules from nacos config:[{"app":"sentinel","clusterConfig":{"acquireRef useStrategy":0,"clientOfflineTime":2000,"fallbackToLocalWhenFail":true,"resourceTimeout":2000,"resourceTimeoutStrategy":0,"sampleCount":10,"strategy":0,"thresholdType":0,"windowInterva lMs":1000},"clusterMode":false,"controlBehavior":0,"count":5.0,"gmtCreate":1608026073444,"gmtModified":1608026073444,"grade":1,"id":2,"ip":"169.254.102.85","limitApp":"default","port": 8720,"resource":"/hello","strategy":0}]
这说明Sentinel Dashboard能从nacos成功拉取流控规则配置
(3)验证流控规则是否生效
此时我们的QPS阈值为5,也就是说1s之间内我们需要超过访问5次,则会被sentinel限流。不断访问/hello资源节点,观察返回
返回Blocked By Sentinel(flow limiting)说明限流规则已经生效。此时实时监控上也会出现通过的QPS数目为5