
一、Sentinel Dashboard 规则联动持久化方案Sentinel是阿里开源的一个流量控制组件它提供了一种流量控制、熔断降级、系统负载保护等功能的解决方案。并且我们通过Sentinel Dashboard可以非常便捷的添加或修改规则策略但是如果细心的小伙伴应该可以发现默认情况下通过Sentinel Dashboard添加或修改的规则并没有进行持久化服务重启后先前的规则也就消失了这对于生产环境肯定是不允许的那如何进行持久化操作呢Sentinel官方已经提供了几种持久化方案比如可以将规则存放在Nacos、Redis、ZK、Apollo等等但是这些操作都没有和Sentinel Dashboard做联动我们需要手动编写规则如json格式存放在持久化引擎中。这里我以Nacos为例通过添加sentinel-datasource-nacos依赖可以将配置持久化到Nacos配置引擎中dependencygroupIdcom.alibaba.csp/groupIdartifactIdsentinel-datasource-nacos/artifactId/dependency比如我们将如下配置在Nacos中[{resource:mylimit,controlBehavior:0,count:1.0,grade:1,limitApp:default,strategy:0}]此时可以在Sentinel Dashboard中看到该规则但是返回来在Sentinel Dashboard添加或修改的规则并没有反向持久化到Nacos中并且随着服务的重启修改的规则会丢失。因此解决上述问题可以从两个方向入手一个是拉模式、一个是推模式拉模式通过sentinel-datasource-extension包扩展读数据源RefreshableDataSource定期扫描目标数据源比如服务器下的某个文件如果发生变动便加载到内存中。以及写数据源WritableDataSource当在Sentinel Dashboard中变动规则时 由服务同步将规则持久化到数据源中。推模式服务通过监有事件通知能力的目标数据源如nacos、redis、zk、Apollo等当目标数据源中的规则发生变动时通过事件监听通知服务更新规则而Sentinel Dashboard则不直接将规则发往服务而是主动持久化到目标数据源中。这种方式需要修改Sentinel Dashboard的源码将原先的通知逻辑修改为存储到目标数据源。两种方式都有各自的优缺点例如拉模式下如果主动改动数据源的数据需要等到下次扫描时才能生效而推模式由于采用事件监听的方式可以保证修改的实时性。但是拉模式下无需修改源码在官方提供的依赖基础上就可以实现而推模式需要根据源码定制化开发需求。下面一起实践下两种方案。为了方便后续的测试这里提前创建一个接口指定资源名称为r1RestControllerRequestMapping(/test)publicclassTestController{SentinelResource(valuer1,blockHandlerblockHandler)GetMapping(/t1)publicStringt1(){returnsuccess;}publicStringblockHandler(BlockExceptionexception){return降级了;}}二、拉模式首先需要引入sentinel-datasource-extension依赖dependencygroupIdcom.alibaba.csp/groupIdartifactIdsentinel-datasource-extension/artifactId/dependency这里使用本地文件的方式存储规则也是extension依赖中已经实现好的方式如果需要存储到其他数据源中可以模仿FileRefreshableDataSource以及FileWritableDataSource进行修改。并且还需要实现InitFunc接口在init方法中指定DataSource初始化逻辑并利用spi机制实现加载。对于规则Sentinel中有多种类型这里实现了 流量、熔断、权限、系统 四个方面的规则持久化publicclassFileDataSourceInitimplementsInitFunc{/** * 规则文件 */privatestaticfinalStringRULE_FILE_PATHSystem.getProperty(user.dir)File.separator;privatestaticfinalStringFLOW_RULE_FILE_NAMEFlowRule.json;privatestaticfinalStringDEGRADE_RULE_FILE_NAMEDegradeRule.json;privatestaticfinalStringAUTHORITY_RULE_FILE_NAMEAuthorityRule.json;privatestaticfinalStringSYSTEM_RULE_FILE_NAMESystemRule.json;Overridepublicvoidinit()throwsException{disposeRules();}privatevoiddisposeRules()throwsFileNotFoundException{StringflowRuleFilePathRULE_FILE_PATHFLOW_RULE_FILE_NAME;StringdegradeRuleFilePathDEGRADE_RULE_FILE_NAMEFLOW_RULE_FILE_NAME;StringauthorityRuleFilePathAUTHORITY_RULE_FILE_NAMEFLOW_RULE_FILE_NAME;StringsystemRuleFilePathSYSTEM_RULE_FILE_NAMEFLOW_RULE_FILE_NAME;FileRefreshableDataSourceListFlowRuleflowRuleReadnewFileRefreshableDataSource(flowRuleFilePath,source-JSON.parseObject((String)source,newTypeReferenceListFlowRule(){}));FileRefreshableDataSourceListDegradeRuledegradeRuleReadnewFileRefreshableDataSource(degradeRuleFilePath,source-JSON.parseObject((String)source,newTypeReferenceListDegradeRule(){}));FileRefreshableDataSourceListAuthorityRuleauthorityRuleReadnewFileRefreshableDataSource(authorityRuleFilePath,source-JSON.parseObject((String)source,newTypeReferenceListAuthorityRule(){}));FileRefreshableDataSourceListSystemRulesystemRuleReadnewFileRefreshableDataSource(systemRuleFilePath,source-JSON.parseObject((String)source,newTypeReferenceListSystemRule(){}));FlowRuleManager.register2Property(flowRuleRead.getProperty());DegradeRuleManager.register2Property(degradeRuleRead.getProperty());SystemRuleManager.register2Property(systemRuleRead.getProperty());AuthorityRuleManager.register2Property(authorityRuleRead.getProperty());WritableDataSourceListFlowRuleflowRuleDatanewFileWritableDataSource(flowRuleFilePath,this::encodeJson);WritableDataSourceListDegradeRuledegradeRuleDatanewFileWritableDataSource(degradeRuleFilePath,this::encodeJson);WritableDataSourceListAuthorityRuleauthorityRuleDatanewFileWritableDataSource(authorityRuleFilePath,this::encodeJson);WritableDataSourceListSystemRulesystemRuleDatanewFileWritableDataSource(systemRuleFilePath,this::encodeJson);WritableDataSourceRegistry.registerFlowDataSource(flowRuleData);WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleData);WritableDataSourceRegistry.registerSystemDataSource(systemRuleData);WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleData);}privateTStringencodeJson(Tt){returnJSON.toJSONString(t);}}然后在resources下创建META-INF.services包然后在该包下新建com.alibaba.csp.sentinel.init.InitFunc文件文件的内容就是上面类的完整包名地址然后启动项目在Sentinel Dashboard中添加一个规则可以在项目根目录发现已经创建了FlowRule.json文件并且存储的内容就是JSON格式的规则信息如果此时将服务重启再次查看Sentinel Dashboard可以发现原有的规则依然存在。并且多次访问t1测试接口可以看到规则依然有效三、推模式推模式下可以通过事件监听更及时的通知到目标服务这里采用Nacos作为目标数据源进行实验。上面实验拉模式可以感觉出Sentinel不同类型的规则是单独控制的这里为了方便演示以流量规则的修改为例进行实践。同时做好Nacos中配置文件名的约定为{application name}-sentinel-{type}例如这里的实验项目名称为sentinel-demo的流量规则的名字为sentinel-demo-sentinel-flow。在Nacos中创建目标配置然后在目标服务中配置flow规则指向该配置上面提到推模式需要修改源码这里先将Sentinel的源码拉取下来gitclone https://github.com/alibaba/Sentinel.git使用IDEA打开项目其中sentinel-dashboard就是下面我们需要修改的项目从Sentinel1.4.0开始sentinel-dashboard项目就提供了DynamicRulePublisher和DynamicRuleProvider两个接口用于实现自定义的规则推送和拉取操作因此我们后续的操作也是基于这两个接口首先在该项目中加入nacos-client的依赖dependencygroupIdcom.alibaba.nacos/groupIdartifactIdnacos-client/artifactIdversion1.4.2/version/dependency先实现一个RuleNacosConfig类用来定义和Nacos的约定信息以及声明出ConfigServiceConfigurationpublicclassRuleNacosConfig{/** * Nacos 配置所在组 */publicstaticfinalStringGROUP_IDDEFAULT_GROUP;/** * Nacos 流量规则的名称后缀 */publicstaticfinalStringFLOW_DATA_ID_POSTFIX-sentinel-flow;/** * Nacos 信息, 最好读取配置信息这里演示先写死 */BeanpublicConfigServicenacosConfigService()throwsException{returnConfigFactory.createConfigService(127.0.0.1:8848);}}实现DynamicRuleProvider根据不同的服务从Nacos中拉取配置Component(flowRuleNacosProvider)publicclassFlowRuleNacosProviderimplementsDynamicRuleProviderListFlowRuleEntity{AutowiredprivateConfigServiceconfigService;OverridepublicListFlowRuleEntitygetRules(StringappName)throwsException{StringrulesconfigService.getConfig(appNameRuleNacosConfig.FLOW_DATA_ID_POSTFIX,RuleNacosConfig.GROUP_ID,3000);if(StringUtil.isEmpty(rules)){returnnewArrayList();}returnJSON.parseArray(rules,FlowRuleEntity.class);}}然后实现DynamicRulePublisher将修改后的规则持久化到NacosComponent(flowRuleNacosPublisher)publicclassFlowRuleNacosPublisherimplementsDynamicRulePublisherListFlowRuleEntity{AutowiredprivateConfigService configService;Overridepublicvoidpublish(String app,ListFlowRuleEntityrules)throws Exception{if(StringUtils.isBlank(app)){thrownewRuntimeException(Server app is null!);}if(rulesnull){return;}configService.publishConfig(appRuleNacosConfig.FLOW_DATA_ID_POSTFIX,RuleNacosConfig.GROUP_ID,JSON.toJSONString(rules));}}接着修改com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1类先将上面创建的FlowRuleNacosProvider和FlowRuleNacosPublisher注入进来下面需要修改/rules和/rule两个接口使用上面我们自定义逻辑首先在 /rules 接口中注释掉原来的sentinelApiClient.fetchFlowRuleOfMachine替换成ListFlowRuleEntityrulesflowRuleNacosProvider.getRules(app);然后在/rule接口注释掉publishRules方法的调用换成flowRuleNacosPublisher.publish(entity.getApp(),repository.findAllByApp(entity.getApp()));停止掉原有的Sentinel Dashboard控制台服务然后本地启动新的Sentinel Dashboard服务或者打包成jar包后启动 。浏览器访问新的Sentinel Dashboard控制台。这里测试增加一个流控规则下面查看Nacos中对应的配置内容已经主动持久化到Nacos中了并且多次访问测试接口规则也依然生效到此整个修改过程就结束了虽然上面仅针对流量控制进行了整合其他模式也是一样的处理方式有兴趣的小伙伴可以继续补充完善。