Sentinel Dashboard集成Nacos目錄:
SpringCloud Alibaba 改造Sentinel Dashboard將流控規則持久化到Nacos 本文
SpringCloud Alibaba 改造Sentinel Dashboard將熔斷規則持久化到Nacos
Sentinel是阿里開源的項目,提供了流量控制、熔斷降級、系統負載保護等多個維度來保障服務之間的穩定性。經歷過Alibaba歷屆雙十一的考驗,其性能的卓越性肯定是不言而喻的。
Sentinel Dashboard是Sentinel提供的圖形化控制台,可以通過Sentinel Dashboard維護流控規則、熔斷規則、熱點規則等。
然而,開源版本的Sentinel Dashboard是無法直接應用於生產環境中的,這是因為通過開源版本的Sentinel Dashboard維護的各項規則是存儲於內存中的,當Sentinel Dashboard重啟,則內存中的各項規則也一並丟失,這在生產上是不被允許的。
在Alibaba Sentinel的githup上,有一篇帖子在生產環境中使用Sentinel,介紹了Sentinel的三種規則管理和推送規則,
如下,而開源版本的Sentinel Dashboard使用的就是其中的原始模式,可以看到是不被推薦在生產環境中應用的。
而另外兩種方式,pull模式和push模式,前者是使用諸如Nacos,利用Nacos Config的特性將Nacos作為純粹的數據源來使用。當需要對流控規則做修改時,需要到Nacos上進行修改,然后Sentinel Dashboard拉取(Pull)Nacos Config上存儲的規則並展示在Dashboard上。
此種方式下,在Dashboard上維護的規則無法直接存儲到Nacos上,修改規則需要到Nacos上進行,但這樣的話就無法利用Dashboard提供的圖形化界面,在Nacos上維護規則也容易出錯,且對人員的要求高,需要非常熟悉規則配置的參數。同時拉取模式無法保證時效性。
所以Sentinel官方推薦的方式是使用Push模式,通過Dashboard配置的規則直接可以存儲在如Nacos之類的數據源上,客戶端通過訂閱Nacos上的配置來拉取規則配置,具體架構如官網上的下圖:
這種方式好是好,但是開源的Sentinel Dashboard並未提供實現,來將配置好的規則存儲於第三方的數據源中,需要直接下載Sentinel Dashboard的源代碼,依據選擇的第三方數據源修改代碼予以實現。本文下面的內容就是介紹如何實現Sentinel Dashboard將規則推送至Nacos上,以及各個應用如何訂閱Nacos上的配置實現流控的。
SpringCloud、SpringCloud Alibaba和SpringBoot三者之間有比較嚴格的版本依賴,在Sentinel開源版本的使用中可能會遇到各種各樣的問題,這其中絕大多數是由於三者版本不匹配導致的,因此建議使用官方推薦的版本。Sentinel官網上並未詳細介紹三者版本之間的關系,而是需要到githup上才能得知具體的依賴關系,介紹的網址如下:
本文使用的是如下的版本:
下載Sentinel Dashboard源代碼
下載地址如下:
https://github.com/alibaba/Sentinel/,使用IDE打開sentinel-dashboard項目
一. Sentinel Dashboard集成Nacos實現流控規則持久化
Sentinel Dashboard的流控規則下的所有操作,都會調用com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1這個類,這個類中包含流控規則本地化(內存中)的CRUD操作,因此流控規則是存儲在內存中的,所以每當重啟Dashboard后,內存中的內容就會丟失。
而com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2中同樣實現了流控規則的CRUD,和V1版本不同的是,它可以實現指定數據源的規則拉取(從指定的數據源中查詢已經配置好的流控規則)和發布(將通過Dashboard維護的內容存儲到指定的數據源中)。
FlowControllerV2中注入了兩個非常重要的類:com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider和com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher分別實現了拉取和發布動作。
這里就需要擴展這兩個類,實現集成Nacos來實現Sentinel Dashboard規則的同步。
1. 首先修改Sentinel Dashboard源碼中的Pom文件,把sentinel-datasource-nacos依賴的<scope>注釋掉
<!-- for Nacos rule publisher sample --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <!-- 使用Nacos作為數據源,需要將scope=test注釋掉 --> <!--<scope>test</scope>--> </dependency>
2. 修改html文件,使得前台維護動作可以調用V2接口
打開:/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html,按照截圖中的介紹修改此html代碼。
打開:/sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html,注釋掉<回到單機頁面>代碼。注釋掉此段代碼的原因是,當我們修改了sidebar.html中的代碼注釋掉了V1版本的頁面,放開了V2版本的頁面后,V2版本的頁面依然保留了原有的在內存中維護規則的入口,就是這個<回到單機頁面>按鈕。
通過點擊<回到單機頁面>按鈕進入到的維護頁面維護的規則依然是存儲在內存中的,為了避免使用者的誤解,故注釋掉此段代碼。
接下來修改簇族鏈路中的流控規則添加按鈕:
打開:\src\main\webapp\resources\app\scripts\controllers\identity.js
修改下圖紅框位置的代碼,將FlowServiceV1修改為FlowServiceV2。
修改前代碼如下:
修改后代碼如下:
3:創建DynamicRuleProvider和DynamicRulePublisher新的實現類
3.1 創建靜態類,定義所需常量
/** * Nacos常量類 * @author gang.wang * 2021年11月8日 */ public class NacosConstants { public static final String DATA_ID_POSTFIX = "-sentinel-flow"; public static final String GROUP_ID = "DEFAULT_GROUP"; }
3.2 創建NacosPropertiesConfiguration類,加載application.properties中的Nacos配置
import org.springframework.boot.context.properties.ConfigurationProperties; /** * 加載Nacos配置 * @author gang.wang * 2021年10月31日 */ @ConfigurationProperties(prefix="sentinel.nacos") public class NacosPropertiesConfiguration { /** * Nacos服務地址 */ private String serverAddr; private String dataId; private String groupId = "DEFAULT_GROUP"; private String namespace; public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getDataId() { return dataId; } public void setDataId(String dataId) { this.dataId = dataId; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } }
3.3 創建NacosConfiguration類,初始化Nacos配置
import java.util.List; import java.util.Properties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.fastjson.JSON; import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.config.ConfigFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; /** * Nacos配置類 * @author gang.wang * 2021年10月31日 */ @EnableConfigurationProperties(NacosPropertiesConfiguration.class) @Configuration public class NacosConfiguration { @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 NacosException { Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr()); properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace()); return ConfigFactory.createConfigService(properties); } }
3.4 創建DynamicRuleProvider的實現類
import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.nacos.api.config.ConfigService; /** * 實現從Nacos配置中心獲取流控規則 * @author gang.wang * 2021年11月8日 */ @Service public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> { private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosProvider.class); @Autowired private NacosPropertiesConfiguration nacosConfigProperties; @Autowired private ConfigService configService; @Autowired private Converter<String, List<FlowRuleEntity>> converter; @Override public List<FlowRuleEntity> getRules(String appName) throws Exception { //定義dataId 應用名+固定后綴 String dataId = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString(); String rules = configService.getConfig(dataId, nacosConfigProperties.getGroupId(), 3000); logger.info("Pull FlowRule from Nacos Config : {}", rules); if(StringUtils.isEmpty(rules)) { return new ArrayList<>(); } return converter.convert(rules); } }
3.5 創建DynamicRulePublisher的實現類
import java.util.List; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.nacos.api.config.ConfigService; /** * 將通過Sentinel Dashboard上維護的流控規則數據持久化到Nacos中 * @author gang.wang * 2021年11月8日 */ @Service public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosPublisher.class); @Autowired private NacosPropertiesConfiguration nacosConfigProperties; @Autowired private ConfigService configService; @Autowired private Converter<List<FlowRuleEntity>, String> converter; @Override public void publish(String appName, List<FlowRuleEntity> rules) throws Exception { if(StringUtils.isBlank(appName)) { logger.error("傳入的AppName為Null"); return ; } if(null == rules) { logger.error("傳入的流控規則數據為null"); return ; } String dataId = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString(); configService.publishConfig(dataId, nacosConfigProperties.getGroupId(), converter.convert(rules)); } }
4:修改FlowControllerV2中DynamicRuleProvider和DynamicRulePublisher的依賴注入
修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2中DynamicRuleProvider和DynamicRulePublisher的依賴注入,引用最新的Nacos Provider和Publisher
修改后如下:
@Autowired //@Qualifier("flowRuleDefaultProvider") //注釋掉原注入 @Qualifier("flowRuleNacosProvider")//Nacos數據源 private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; /** * 動態規則的發布,將在Sentinel Dashboard中修改的規則同步到指定數據源中。 */ @Autowired //@Qualifier("flowRuleDefaultPublisher") @Qualifier("flowRuleNacosPublisher")//Nacos數據源 private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
5:添加Nacos配置信息
修改application.properties文件的內容添加Nacos服務器配置。
# 定義Nacos服務器信息 sentinel.nacos.serverAddr=127.0.0.1:8848 sentinel.nacos.namespace=37c7c263-bdf1-41db-9f34-bf1094111111 sentinel.nacos.group-id=DEFAULT_GROUP
6:重新打包並運行我們修改好的Sentinel Dashboard
啟動Sentinel Dashboard項目,瀏覽器中訪問:http://127.0.0.1:8080/#/login 輸入用戶名/密碼=sentinel/sentinel,就可以順利登陸Sentinel Dashboard控制台了!
此時進入控制台后,左邊菜單中還看不到任何菜單信息。因為此刻還沒有任何項目連接到Sentinel Dashboard上。
7:創建SpringBoot應用並連接到Sentinel Dashboard上
7.1 應用的Pom文件中添加sentinel和nacos相關依賴

<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>com.alibaba.sentinel</groupId> <artifactId>sentinel-dashboard-test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>sentinel-dashboard-test</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- Spring Cloud --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!-- Database --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc7</artifactId> <version>12.1.0.1</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <!-- Other --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
7.2 修改應用的yml文件,添加Nacos和Sentinel Dashboard配置
spring:
application:
name: sentinel-dashboard-test
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
cloud:
nacos:
discovery:
namespace: 37c7c263-bdf1-41db-9f34-bf10948be752
server-addr: 127.0.0.1:8848
weight: 1
config:
namespace: 37c7c263-bdf1-41db-9f34-bf10948be752
file-extension: yml
max-retry: 5
name: sentinel-dashboard-test
refresh-enabled: true
prefix:
sentinel:
transport:
dashboard: 127.0.0.1:8080
datasource:
flow:
nacos:
server-addr: 127.0.0.1:8848
namespace: 37c7c263-bdf1-41db-9f34-bf10948be752
data-id: ${spring.application.name}-sentinel-flow
group-id: DEFAULT_GROUP
data-type: json
rule-type: flow
7.3 創建一個接口
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; /** * @author gang.wang * 2021年9月15日 */ @RestController public class SentinelDemoOneController { private Logger logger = LoggerFactory.getLogger(SentinelDemoOneController.class); @SentinelResource(value = "hello", blockHandler = "blockHandlerHello") @GetMapping("/say") public String hello() { return "hello, Gary!"; } public String blockHandlerHello(BlockException ex) { logger.error("當前請求已被限流", ex); return "當前請求已被限流"; } }
7.4 啟動應用並訪問定義的接口
啟動應用,並使用如Postman訪問定義好的/say接口。因為Sentinel Dashboard是使用懶加載的模式檢測需要攔截的接口(http://127.0.0.1:8083/say),因此啟動應用后如果不調用幾次接口,則在Sentinel Dashboard中還是看不到我們定義好的接口信息。
刷新Sentinel Dashboard頁面,可以看到應用了
點擊實時監控菜單,可以看到剛才我們訪問的接口的監控數據
點擊流控規則菜單,新建一條流控規則:
這里我們為了驗證方便,將qps的閾值設置為1,即每秒允許一個請求通過。
8:驗證流控規則是否生效
再次使用Postman快速訪問這個接口,可以看到有時候會成功,有時候會失敗,失敗時的返回結果如下:
9:驗證通過Sentinel Dashboard配置好的流控規則是否正確存儲在Nacos上
訪問Nacos控制台,查看對應的Namespace下是否有dataId = sentinel-dashboard-test-sentinel-flow的配置。
可見,通過Sentinel Dashboard配置的流控規則已經自動存儲在Nacos中了!
至此,Sentinel Dashboard與Nacos之間對於流控規則的同步已經完成了。