在之前的兩篇教程中我們分別介紹了如何將Sentinel的限流規則存儲到Nacos和Apollo中。同時,在文末的思考中,我都指出了這兩套整合方案都存在一個不足之處:不論采用什么配置中心,限流規則都只能通過Nacos界面或Apollo界面來完成修改才能得到持久化存儲,而在Sentinel Dashboard中修改限流規則雖然可以生效,但是不會被持久化到配置中心。而在這兩個配置中心里存儲的數據是一個Json格式,當存儲的規則越來越多,對該Json配置的可讀性與可維護性會變的越來越差。所以,下面我們就來繼續探討這個不足之處,并給出相應的解決方案。本文以Apollo存儲為例,下一篇介紹Nacos的改在示例。
問題分析
在實際操作之前,我們先通過下圖了解一下之前我們所實現的限流規則持久化方案的配置數據流向圖:
-
藍色箭頭
代表了限流規則由配置中心
發起修改的更新路徑 -
橙色箭頭
代表了限流規則由Sentinel Dashboard
發起修改的更新路徑
從圖中可以很明顯的看到,Sentinel Dashboard
與業務服務之間本身是可以互通獲取最新限流規則的,這在沒有整合配置中心來存儲限流規則的時候就已經存在這樣的機制。最主要的區別是:配置中心的修改都可以實時的刷新到業務服務,從而被Sentinel Dashboard
讀取到,但是對于這些規則的更新到達各個業務服務之后,并沒有一個機制去同步到配置中心,作為配置中心的客戶端也不會提供這樣的逆向更新方法。
改造方案
關于如何改造,現來解讀一下官方文檔中關于這部分的說明:
要通過 Sentinel 控制臺配置集群流控規則,需要對控制臺進行改造。我們提供了相應的接口進行適配。
從 Sentinel 1.4.0 開始,我們抽取出了接口用于向遠程配置中心推送規則以及拉取規則:
- DynamicRuleProvider<T>: 拉取規則
- DynamicRulePublisher<T>: 推送規則
對于集群限流的場景,由于每個集群限流規則都需要唯一的 flowId,因此我們建議所有的規則配置都通過動態規則源進行管理,并在統一的地方生成集群限流規則。
我們提供了新版的流控規則頁面,可以針對應用維度推送規則,對于集群限流規則可以自動生成 flowId。用戶只需實現 DynamicRuleProvider 和 DynamicRulePublisher 接口,即可實現應用維度推送(URL: /v2/flow)。
這段內容什么意思呢?簡單的說就是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來存儲限流配置。 - 其他如Cluster、Namepsace都采用了默認值:default和application,這個讀者有特殊需求可以做對應的修改。
第六步:實現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);
}
}
- 這里的大部分內容,如:env、flowDataId、app說明與上一步中的實現一致
-
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
項目:
- Github:https://github.com/dyc87112/SpringCloud-Learning/
- Gitee:https://gitee.com/didispace/SpringCloud-Learning/
如果您對這些感興趣,歡迎star、follow、收藏、轉發給予支持!
系列回顧
- 《Spring Cloud Alibaba基礎教程:使用Nacos實現服務注冊與發現》
- 《Spring Cloud Alibaba基礎教程:支持的幾種服務消費方式》
- 《Spring Cloud Alibaba基礎教程:使用Nacos作為配置中心》
- 《Spring Cloud Alibaba基礎教程:Nacos配置的加載規則詳解》
- 《Spring Cloud Alibaba基礎教程:Nacos配置的多環境管理》
- 《Spring Cloud Alibaba基礎教程:Nacos配置的多文件加載與共享配置》
- 《Spring Cloud Alibaba基礎教程:Nacos的數據持久化》
- 《Spring Cloud Alibaba基礎教程:Nacos的集群部署》
- 《Spring Cloud Alibaba基礎教程:使用Sentinel實現接口限流》
- 《Spring Cloud Alibaba基礎教程:Sentinel使用Nacos存儲規則》
- 《Spring Cloud Alibaba基礎教程:Sentinel使用Apollo存儲規則》