在以前的兩篇教程中咱們分別介紹瞭如何將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、收藏、轉發給予支持!