手把手教你改造 Sentinel Dashboard 實現配置持久化
一. 概述
Sentinel客戶端默認情況下接收到 Dashboard 推送的規則配置后,可以實時生效。但是有一個致命缺陷,Dashboard和業務服務并沒有持久化這些配置,當業務服務重啟后,這些規則配置將全部丟失。
Sentinel 提供兩種方式修改規則:
- 通過 API 直接修改 (
loadRules) - 通過
DataSource適配不同數據源修改
通過 API 修改比較直觀,可以通過以下幾個 API 修改不同的規則:
FlowRuleManager.loadRules(List<FlowRule> rules); // 修改流控規則
DegradeRuleManager.loadRules(List<DegradeRule> rules); // 修改降級規則
手動修改規則(硬編碼方式)一般僅用于測試和演示,生產上一般通過動態規則源的方式來動態管理規則。
上述 loadRules() 方法只接受內存態的規則對象,但更多時候規則存儲在文件、數據庫或者配置中心當中。DataSource 接口給我們提供了對接任意配置源的能力。相比直接通過 API 修改規則,實現 DataSource 接口是更加可靠的做法。
我們推薦通過控制臺設置規則后將規則推送到統一的規則中心,客戶端實現 ReadableDataSource 接口端監聽規則中心實時獲取變更,流程如下:

DataSource 擴展常見的實現方式有:
- 拉模式:客戶端主動向某個規則管理中心定期輪詢拉取規則,這個規則中心可以是 RDBMS、文件,甚至是 VCS 等。這樣做的方式是簡單,缺點是無法及時獲取變更;
- 推模式:規則中心統一推送,客戶端通過注冊監聽器的方式時刻監聽變化,比如使用 Nacos、Zookeeper 等配置中心。這種方式有更好的實時性和一致性保證。
Sentinel 目前支持以下數據源擴展:
Sentinel開源版在Push模式下只實現了 路徑2,也就是Nacos到業務服務之間的規則同步;路徑1 Dashboard配置修改寫入Nacos并沒有實現,在后文中我們會介紹如何修改 Dashboard 源碼完成配置的寫入。
二. 從 Nacos 加載規則配置
首先,我們先來看看如何使用Sentinel官方提供的 sentinel-datasource-nacos 從Nacos加載規則配置。
第一步:引入依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.6</version>
</dependency>
第二步:配置規則自動加載
package cn.bigcoder.demo.sentinel.sentineldemo.demos.config;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.PropertyKeyConst;
import java.util.List;
import java.util.Properties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class SentinelRuleConfiguration implements ApplicationListener<ContextRefreshedEvent> {
private static final String remoteAddress = "10.10.10.12:8848";
// nacos group
private static final String groupId = "SENTINEL_GROUP";
// nacos dataId
private static final String dataId = "sentinel-demo";
private static final String NACOS_NAMESPACE_ID = "SENTINEL";
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, remoteAddress);
// properties.put(PropertyKeyConst.NAMESPACE, NACOS_NAMESPACE_ID);
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(properties, groupId,
dataId,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
}
第三步:往Nacos中寫入配置
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import java.util.Properties;
public class NacosConfigSender {
public static void main(String[] args) throws Exception {
final String groupId = "SENTINEL_GROUP";
final String dataId = "sentinel-demo-flow-rules";
// 創建ConfigService實例
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, "10.10.10.12:8848");
// 指定namespace
properties.put(PropertyKeyConst.NAMESPACE, "SENTINEL");
final String rule = "[\n"
+ " {\n"
+ " \"resource\": \"GET:/user/getById\",\n"
+ " \"controlBehavior\": 0,\n"
+ " \"count\": 1,\n"
+ " \"grade\": 1,\n"
+ " \"limitApp\": \"default\",\n"
+ " \"strategy\": 0\n"
+ " }\n"
+ "]";
ConfigService configService = NacosFactory.createConfigService(properties);
System.out.println(configService.publishConfig(dataId, groupId, rule));
}
}
執行完后,Nacos中就會出現對應的配置:

第四步:啟動項目,驗證規則配置是否生效
訪問 http://127.0.0.1:8719/getParamRules?type=flow 即可看到業務服務內存中加載到的規則配置

并發執行 /user/getById 接口,可以發現接口被成功限流,1s內的10次請求,只有一次成功。

三. Dashboard存在的問題
使用此方案雖然解決了配置規則配置持久化的問題,但是在Dashboard上修改配置仍然是通過業務服務暴露的接口進行的配置同步。業務服務既可以接收 Nacos 配置變更,又可以接收Dashboard的配置變更,控制臺的變更的配置并沒有同步到Nacos,應用重啟后Sentinel控制臺修改的配置仍然會全部丟失:

一個理想的情況是Sentinel控制臺規則配置讀取至 Nacos 而不是內存,在控制臺修改/新增的配置寫入Nacos,當Nacos配置發生變更時,配置進而自動同步至業務服務:

當然存儲媒介可以根據情況選用別的組件:ZooKeeper, Redis, Apollo, etcd
很可惜的是,阿里官方開源的Sentinel控制臺并沒有實現將規則配置寫入其他中間件的能力。它默認只支持將配置實時推送至業務服務,所以我們在生產環境中想要使用 Sentinel Dashboard 需要自行修改其源碼,將其配置同步邏輯改為寫入我們所需要的中間件中。
四. 修改Sentinel Dashboard源碼
4.1 準備工作
首先通過git拉取下載源碼,導入idea工程:
https://github.com/alibaba/Sentinel
本文源碼修改基于 Sentinel 1.8.8 版本,所有修改的源碼可參考:
https://github.com/bigcoder84/Sentinel
4.1.1 流控規則接口
Sentinel Dashboard的流控規則下的所有操作,都會調用Sentinel-Dashboard源碼中的FlowControllerV1類,這個類中包含流控規則本地化的CRUD操作:

在com.alibaba.csp.sentinel.dashboard.controller.v2包下存在一個FlowControllerV2;類,這個類同樣提供流控規則的CURD,與V1不同的是,它可以實現指定數據源的規則拉取和發布。
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);
@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
//....
}
官方說明:從 Sentinel 1.4.0 開始,我們抽取出了接口用于向遠程配置中心推送規則以及拉取規則。
DynamicRuleProvider<T>: 拉取規則DynamicRulePublisher<T>: 推送規則以 Nacos 為例,若希望使用 Nacos 作為動態規則配置中心,用戶可以提取出相關的類,然后只需在
FlowControllerV2中指定對應的 bean 即可開啟 Nacos 適配
FlowControllerV2依賴兩個非常重要的類
- DynamicRuleProvider:動態規則的拉取,從指定數據源中獲取控制后在Sentinel Dashboard中展示。
- DynamicRulePublisher:動態規則發布,將在Sentinel Dashboard中修改的規則同步到指定數據源中。
只需要擴展這兩個類,然后集成Nacos來實現Sentinel Dashboard規則同步。
4.1.2 需要改造的頁面入口
簇點鏈路:

由于該頁面的“流控”配置是對單節點進行配置的,所以理論上該頁面的URL是不用改的
流控規則:

上述位置我們都需要改造對應前端代碼,使之調用的接口更改為我們新的V2接口上。
4.2 源碼改造
4.2.1 在pom.xml文件中去掉test scope注釋
這是因為官方提供的Nacos持久化用例都是在test目錄下,所以scope需要去除test,需要sentinel-datasource-nacos包的支持。之后將修改好的源碼放在源碼主目錄下,而不是繼續在test目錄下。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!--<scope>test</scope>-->
</dependency>
4.2.2 創建Nacos配置
我們采用官方的約束,即默認 Nacos 適配的 dataId 和 groupId 約定如下:
- groupId: SENTINEL_GROUP
- 流控規則 dataId: {appName}-flow-rules,比如應用名為 appA,則 dataId 為 appA-flow-rules
所以不需要修改NacosConfigUtil.java了,但這是展示是為了步驟的完整性。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "sentinel.nacos")
public class NacosPropertiesConfiguration {
private String serverAddr;
private String groupId = "SENTINEL_GROUP"; // 默認分組
private String namespace;
// 省略 getter/setter
}
然后配置sentinel-dashboar/resources/application.properties中配置nacos配置,以為sentinel.nacos為前綴:
# nacos config server
sentinel.nacos.serverAddr=127.0.0.1:8848
sentinel.nacos.namespace=
sentinel.nacos.group-id=SENTINEL-GROUP
4.2.3 改造NacosConfig,創建NacosConfigService
package com.alibaba.csp.sentinel.dashboard.config;
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 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;
@EnableConfigurationProperties(NacosPropertiesConfiguration.class)
@Configuration
public class NacosConfig {
@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 Exception {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
return ConfigFactory.createConfigService(properties);
}
}
NacosConfig主要做兩件事:
1) 注入Convert轉換器,將 FlowRuleEntity 使用序列化為JSON字符串,以及將JSON字符串反序列化為 FlowRuleEntity。
2) 注入Nacos配置服務ConfigService
4.2.4 實現 DynamicRulePublisher 和 DynamicRuleProvider 接口完成配置的持久化和遠程加載
在 test 包下,已經有Sentinel官方的實現了,我們只需要將其拷貝至 src 目錄下即可:

FlowRuleNacosProvider:用于從Nacos中拉取規則配置。
package com.alibaba.csp.sentinel.dashboard.rule;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.util.NacosConfigUtil;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<FlowRuleEntity>> converter;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
FlowRuleNacosPublisher:用于將配置保存至Nacos中
package com.alibaba.csp.sentinel.dashboard.rule;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.util.NacosConfigUtil;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<FlowRuleEntity>, String> converter;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, converter.convert(rules));
}
}
4.2.5 修改FlowControllerV2類將Nacos實現類注入進去

@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);
@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
// 省略
}
到這里所有流控規則相關的后端接口都已經改造完畢,我們需要接著改造前端頁面,將頁面請求的接口全部換成V2新接口。
4.2.6 修改前端“流控規則”路由配置(sidebar.html)
找到 resources/app/scripts/directives/sidebar/sidebar.html 文件,該文件是用來渲染左側路由的:

我們需要將 “流控規則” 路由跳轉的頁面由 app/views/flow_v1.html 更換為 app/views/flow_v2.html,因為 flow_v2.html 頁面中調用的后端接口全部都是 v2接口。
修改flowV1為flow,去掉V1,這樣的話會調用FlowControllerV2接口
<!--<li ui-sref-active="active" ng-if="!entry.isGateway">
<a ui-sref="dashboard.flowV1({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i> 流控規則</a>
</li>-->
<!-- 修改為flow,直接調用FlowControllerV2 -->
<li ui-sref-active="active" ng-if="!entry.isGateway">
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i> 流控規則</a>
</li>
這樣就可以通過js跳轉至FlowControllerV2了
.state('dashboard.flow', {
templateUrl: 'app/views/flow_v2.html',
url: '/v2/flow/:app',
controller: 'FlowControllerV2',
resolve: {
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'sentinelDashboardApp',
files: [
'app/scripts/controllers/flow_v2.js',
]
});
}]
}
})
4.2.7 修改前端“簇點鏈路”中流控配置的接口
根據 app/scripts/directives/sidebar/sidebar.html 觸點鏈路路由調用js方法可知,最終路由轉發到了 app/views/identity.html 頁面:
<!-- app/scripts/directives/sidebar/sidebar.html -->
<li ui-sref-active="active" ng-if="!entry.isGateway">
<a ui-sref="dashboard.identity({app: entry.app})">
<i class="glyphicon glyphicon-list-alt"></i> 簇點鏈路</a>
</li>
// app/scripts/app.js
.state('dashboard.identity', {
templateUrl: 'app/views/identity.html',
url: '/identity/:app',
controller: 'IdentityCtl',
resolve: {
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'sentinelDashboardApp',
files: [
'app/scripts/controllers/identity.js',
]
});
}]
}
})
在 app/views/identity.html 頁面中,我們需要將“流控”彈窗的保存按鈕調用的接口換成V2版本,

addNewFlowRule 方法在 app/scripts/controllers/identity.js 文件中:
$scope.addNewFlowRule = function (resource) {
if (!$scope.macInputModel) {
return;
}
var mac = $scope.macInputModel.split(':');
flowRuleDialogScope = $scope.$new(true);
flowRuleDialogScope.currentRule = {
enable: false,
strategy: 0,
grade: 1,
controlBehavior: 0,
resource: resource,
limitApp: 'default',
clusterMode: false,
clusterConfig: {
thresholdType: 0
},
app: $scope.app,
ip: mac[0],
port: mac[1]
};
flowRuleDialogScope.flowRuleDialog = {
title: '新增流控規則',
type: 'add',
confirmBtnText: '新增',
saveAndContinueBtnText: '新增并繼續添加',
showAdvanceButton: true
};
// $scope.flowRuleDialog = {
// showAdvanceButton : true
// };
flowRuleDialogScope.saveRule = saveFlowRule;
flowRuleDialogScope.saveRuleAndContinue = saveFlowRuleAndContinue;
flowRuleDialogScope.onOpenAdvanceClick = function () {
flowRuleDialogScope.flowRuleDialog.showAdvanceButton = false;
};
flowRuleDialogScope.onCloseAdvanceClick = function () {
flowRuleDialogScope.flowRuleDialog.showAdvanceButton = true;
};
flowRuleDialog = ngDialog.open({
template: '/app/views/dialog/flow-rule-dialog.html',
width: 680,
overlay: true,
scope: flowRuleDialogScope
});
};
在這個方法中,會調用 saveFlowRule 方法保存流控規則:
function saveFlowRule() {
if (!FlowService.checkRuleValid(flowRuleDialogScope.currentRule)) {
return;
}
FlowService.newRule(flowRuleDialogScope.currentRule).success(function (data) {
if (data.code === 0) {
flowRuleDialog.close();
let url = '/dashboard/flow/' + $scope.app;
$location.path(url);
} else {
alert('失敗:' + data.msg);
}
}).error((data, header, config, status) => {
alert('未知錯誤');
});
}
在這個方法中,會調用 FlowService.newRule 方法發送HTTP請求新建規則,成功后會將頁面重定向至 '/dashboard/flow/' + $scope.app,所以我們需要改兩個地方:
-
將FlowService改成V2版本

-
將重定向頁面跳轉至
'/dashboard/v2/flow/' + $scope.app
4.3.8 重新打包項目
進入 sentinel-dashboard 目錄,執行下列命令重新打包:
mvn clean package -Dmaven.test.skip
4.3 測試

可以看到修改后的Dashboard成功將配置寫入Nacos中,Nacos配置發生變更,也同時通知了訂閱這些配置的客戶端,使得業務服務能實時更新流控配置,即使業務服務重啟,之前仍能正常從Nacos中拉取配置。
五. 總結
本文詳細介紹了如何利用Nacos實現Sentinel Dashboard配置的持久化,解決了業務服務重啟后配置丟失的問題。通過以下幾個步驟,我們成功實現了配置的動態管理和持久化存儲:
- 引入Nacos依賴:在項目中添加了
sentinel-datasource-nacos依賴,為后續集成打下基礎。 - 配置自動加載:通過實現
ReadableDataSource接口,配置了規則自動從Nacos加載到Sentinel的流程。 - Nacos配置寫入:通過編寫
NacosConfigSender類,實現了向Nacos寫入配置的功能。 - Dashboard源碼改造:針對Dashboard存在的問題,通過修改前后端源碼,實現了配置的持久化存儲和同步更新。
通過這一系列的改造,我們不僅提高了Sentinel Dashboard的可用性和穩定性,還增強了其在生產環境中的實用性。現在,即使在業務服務重啟的情況下,配置也不會丟失,確保了服務的連續性和一致性。
本文只是講解了“流控規則”持久化的源碼修改過程,如果其它模塊也有持久化的需求,也可以參考此過程進行相應的源碼修改。
本文參考至:
dynamic-rule-configuration | Sentinel (sentinelguard.io)
Sentinel Dashboard(基于1.8.1)流控規則持久化到Nacos——涉及部分Sentinel Dashboard源碼改造 - JJian - 博客園 (cnblogs.com)

浙公網安備 33010602011771號