在以前的两篇教程中咱们分别介绍了如何将Sentinel的限流规则存储到Nacos和Apollo中。同时,在文末的思考中,我都指出了这两套整合方案都存在一个不足之处:不论采用什么配置中心,限流规则都只能经过Nacos界面或Apollo界面来完成修改才能获得持久化存储,而在Sentinel Dashboard中修改限流规则虽然能够生效,可是不会被持久化到配置中心。而在这两个配置中内心存储的数据是一个Json格式,当存储的规则愈来愈多,对该Json配置的可读性与可维护性会变的愈来愈差。因此,下面咱们就来继续探讨这个不足之处,并给出相应的解决方案。本文以Apollo存储为例,下一篇介绍Nacos的改在示例。html
在实际操做以前,咱们先经过下图了解一下以前咱们所实现的限流规则持久化方案的配置数据流向图:java
蓝色箭头
表明了限流规则由配置中心
发起修改的更新路径橙色箭头
表明了限流规则由Sentinel Dashboard
发起修改的更新路径从图中能够很明显的看到,Sentinel Dashboard
与业务服务之间自己是能够互通获取最新限流规则的,这在没有整合配置中心来存储限流规则的时候就已经存在这样的机制。最主要的区别是:配置中心的修改均可以实时的刷新到业务服务,从而被Sentinel Dashboard
读取到,可是对于这些规则的更新到达各个业务服务以后,并无一个机制去同步到配置中心,做为配置中心的客户端也不会提供这样的逆向更新方法。git
关于如何改造,现来解读一下官方文档中关于这部分的说明:github
要经过 Sentinel 控制台配置集群流控规则,须要对控制台进行改造。咱们提供了相应的接口进行适配。spring
从 Sentinel 1.4.0 开始,咱们抽取出了接口用于向远程配置中心推送规则以及拉取规则:api
- DynamicRuleProvider<T>: 拉取规则
- DynamicRulePublisher<T>: 推送规则
对于集群限流的场景,因为每一个集群限流规则都须要惟一的 flowId,所以咱们建议全部的规则配置都经过动态规则源进行管理,并在统一的地方生成集群限流规则。app
咱们提供了新版的流控规则页面,能够针对应用维度推送规则,对于集群限流规则能够自动生成 flowId。用户只需实现 DynamicRuleProvider 和 DynamicRulePublisher 接口,便可实现应用维度推送(URL: /v2/flow)。ide
这段内容什么意思呢?简单的说就是Sentinel Dashboard
经过DynamicRuleProvider
和DynamicRulePublisher
两个接口来获取和更新应用的动态规则。默认状况下,就如上一节中Sentinel Dashboard
与各业务服务之间的两个箭头,一个接口负责获取规则,一个接口负责更新规则。微服务
因此,只须要经过这两个接口,实现对配置中心中存储规则的读写,就能实现Sentinel Dashboard
中修改规则与配置中心存储同步的效
图以下:测试
![图片上传中...]
其中,绿色箭头为公共公共部分,即:不论从培中心修改,仍是从Sentinel Dashboard
修改都会触发的操做。这样的话,从上图的两处修改起点看,全部涉及的部分都能获取到一致的限流规则了。
下面继续说说具体的代码实现,这里参考了Sentinel Dashboard
源码中关于Apollo实现的测试用例。可是因为考虑到与Spring Cloud Alibaba的结合使用,略做修改。
第一步:修改pom.xml
中的Apollo OpenAPi的依赖,将<scope>test</scope>
注释掉,这样才能在主程序中使用。
<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-openapi</artifactId> <version>1.2.0</version> <!--<scope>test</scope>--> </dependency>
第二步:找到resources/app/scripts/directives/sidebar/sidebar.html
中的这段代码:
<li ui-sref-active="active"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则 </a> </li>
修改成:
<li ui-sref-active="active"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则 </a> </li>
第三步:在com.alibaba.csp.sentinel.dashboard.rule
包下新建一个apollo包,用来编写针对Apollo的扩展实现。
第四步:建立Apollo的配置类,定义Apollo的portal访问地址以及第三方应用访问的受权Token(经过Apollo管理员帐户登陆,在“开放平台受权管理”功能中建立),具体代码以下:
@Configuration public class ApolloConfig { @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 ApolloOpenApiClient apolloOpenApiClient() { ApolloOpenApiClient client = ApolloOpenApiClient.newBuilder() .withPortalUrl("https://apollo.xxx.com") // TODO 根据实际状况修改 .withToken("open api token") // TODO 根据实际状况修改 .build(); return client; } }
第五步:实现Apollo的配置拉取实现。
@Component("flowRuleApolloProvider") public class FlowRuleApolloProvider implements DynamicRuleProvider<List<FlowRuleEntity>> { @Autowired private ApolloOpenApiClient apolloOpenApiClient; @Autowired private Converter<String, List<FlowRuleEntity>> converter; @Value("${env:DEV}") private String env; @Override public List<FlowRuleEntity> getRules(String appName) throws Exception { // flowDataId对应 String flowDataId = "sentinel.flowRules"; OpenNamespaceDTO openNamespaceDTO = apolloOpenApiClient.getNamespace(appName, env, "default", "application"); String rules = openNamespaceDTO .getItems() .stream() .filter(p -> p.getKey().equals(flowDataId)) .map(OpenItemDTO::getValue) .findFirst() .orElse(""); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return converter.convert(rules); } }
getRules
方法中的appName
参数是Sentinel中的服务名称,这里直接经过这个名字获取Apollo配置是因为Apollo中的项目AppId与之一致,若是存在不一致的状况,则须要本身作转换。env
属性,主要因为咱们在使用Apollo的时候,经过启动参数来控制不一样环境。因此这样就能在不一样环境区分不一样的限流配置了。flowDataId
对应各个微服务应用中定义的spring.cloud.sentinel.datasource.ds.apollo.flowRulesKey
配置,即:Apollo中使用了什么key来存储限流配置。第六步:实现Apollo的配置推送实现。
@Component("flowRuleApolloPublisher") public class FlowRuleApolloPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { @Autowired private ApolloOpenApiClient apolloOpenApiClient; @Autowired private Converter<List<FlowRuleEntity>, String> converter; @Value("${env:DEV}") private String env; @Override public void publish(String app, List<FlowRuleEntity> rules) throws Exception { String flowDataId = "sentinel.flowRules"; AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } OpenItemDTO openItemDTO = new OpenItemDTO(); openItemDTO.setKey(flowDataId); openItemDTO.setValue(converter.convert(rules)); openItemDTO.setComment("modify by sentinel-dashboard"); openItemDTO.setDataChangeCreatedBy("apollo"); apolloOpenApiClient.createOrUpdateItem(app, env, "default", "application", openItemDTO); // Release configuration NamespaceReleaseDTO namespaceReleaseDTO = new NamespaceReleaseDTO(); namespaceReleaseDTO.setEmergencyPublish(true); namespaceReleaseDTO.setReleaseComment("release by sentinel-dashboard"); namespaceReleaseDTO.setReleasedBy("apollo"); namespaceReleaseDTO.setReleaseTitle("release by sentinel-dashboard"); apolloOpenApiClient.publishNamespace(app, env, "default", "application", namespaceReleaseDTO); } }
openItemDTO.setDataChangeCreatedBy("apollo");
和namespaceReleaseDTO.setReleasedBy("apollo");
这两句须要注意一下,必须设置存在而且有权限的用户,否则会更新失败。第七步:修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2
中DynamicRuleProvider
和DynamicRulePublisher
注入的Bean,改成上面咱们编写的针对Apollo的实现:
@Autowired @Qualifier("flowRuleApolloProvider") private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; @Autowired @Qualifier("flowRuleApolloPublisher") private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
本文介绍内容的客户端代码,示例读者能够经过查看下面仓库中的alibaba-sentinel-dashboard-apollo
项目:
若是您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!