# Sentinel与Nacos实现动态数据源
# 介绍
按照官方的文档定义,Sentinel是一个分布式系统的流量防卫兵,是面向分布式服务架构的流量控制组件,主要提供限流、熔断等服务治理的相关的功能,来保障微服务的稳定性。
# 限流与熔断
注意
看到了这里,你可能会对限流、熔断还不知道是什么概念,有必要知道一下, 那什么是限流和熔断呢?我对限流、熔断的一些自我理解,给大家分享一下:
限流:限流还是比较好理解的,比如一个项目的上线之前,经过了性能测试评估,通常使用TPS(系统吞吐量)对流量的来进行一种描述,比如服务在 TPS 到达性能测试评估的阈值 1w/s 事,系统的资料利用率飘升,与此同时响应时间也急剧增大,为了保障服务的稳定性,有必要控制单位时间的请求量,可以采用拒绝、排队等策略,实现流量的削峰填谷,这就是限流为什么愈发重要的原因。
熔断:当系统中某一个服务因为某种原因突然变得不可用或响应过慢,例如某个线程池卡住了,导致发送到它上面的请求会出现超时而报错,这样整个系统的大部分请求出现超时报错,影响了整个系统的可用性,因此对这个服务的调用进行快速失败,不再继续调用目标服务,直接返回,快速释放资源,来保证整个系统服务的可用性,避免造成其它的服务连锁反应,这就是自我理解的熔断机制。
有了上面的基本认识,接下来会进行Sentinel在生产中控制台改造和实现动态数据源绑定来做一下分享。
# Sentinel设计理念
在 Sentinel 中主要的有如下几个角色:控制台、规则数据源、Sentinel 客户端。
Sentinel 的角色之间的关系图:

# Sentinel改造思路
在官方的文档中,sentinel-dashboard 控制台只支持限流、熔断等限流配置规则存储在内存中,在页面上操作来更新规则,都无法避免一个问题,那就是服务重新后,规则就丢失了,因为默认情况下规则是保存在内存中的,缺点是非常明显,无法用于实际生产中使用,所以在实际生产中需要对 sentinel-dashboard 进行一定的改造,引入动态数据源,例如:引入 Nacos 对限流等配置进行持久化存储,改造后最终结果,如下图所示:

# (一)Sentinel改造思路方向
从官方提供的demo代码如下图:

官方的 Nacos-Datasource 主要介绍了基于 Nacos 如何构建 Sentinel 动态数据源提供了思路。
# (二)限流配置规则存储
通过查看 NacosConfigSender 类,该类主要作用是将配置写入到 Nacos 中,进行配置规则持久化处理,路径为 /groupId/dataId ,对应的配置规则 value 值存储为 json 字符串,存储所有的限流规则,代码如下:
public class NacosConfigSender {
public static void main(String[] args) throws Exception {
final String remoteAddress = "localhost";
final String groupId = "Sentinel:Demo";
final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule";
// 这一部分是配置规则,有限流、熔断、系统级别的规则
final String rule = "[\n"
+ " {\n"
+ " \"resource\": \"TestResource\",\n"
+ " \"controlBehavior\": 0,\n"
+ " \"count\": 5.0,\n"
+ " \"grade\": 1,\n"
+ " \"limitApp\": \"default\",\n"
+ " \"strategy\": 0\n"
+ " }\n"
+ "]";
// 这一段是 Nacos 提供的 API操作,对应最终改造结果图的 api 部分
ConfigService configService = NacosFactory.createConfigService(remoteAddress);
System.out.println(configService.publishConfig(dataId, groupId, rule));
}
}
# (三)Sentinel客户端实时监听Nacos配置规则
通过查看 NacosDataSourceDemo 类,该测试类主要实现了存储规则的配置存储后,需要客户端能动态监听到 Nacos 的变化,从而配置规则实时生效。核心代码如下:
public static void main(String[] args) {
if (isDemoNamespace) {
loadMyNamespaceRules();
} else {
loadRules();
}
// Assume we config: resource is `TestResource`, initial QPS threshold is 5.
FlowQpsRunner runner = new FlowQpsRunner(KEY, 1, 100);
runner.simulateTraffic();
runner.tick();
}
private static void loadRules() {
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
private static void loadMyNamespaceRules() {
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());
}
在这里,客户端在启动的时候会调用相关的方法来加载限流的相关配置,它是怎么动态感知到配置中心有改动呢,进而实时生效呢?我们通过去查看 NacosDataSource 这个类,代码如下:
/**
*
* @param properties properties for construct {@link ConfigService} using {@link NacosFactory#createConfigService(Properties)}
* @param groupId group ID, cannot be empty
* @param dataId data ID, cannot be empty
* @param parser customized data parser, cannot be empty
*/
public NacosDataSource(final Properties properties, final String groupId, final String dataId,
Converter<String, T> parser) {
super(parser);
if (StringUtil.isBlank(groupId) || StringUtil.isBlank(dataId)) {
throw new IllegalArgumentException(String.format("Bad argument: groupId=[%s], dataId=[%s]",
groupId, dataId));
}
AssertUtil.notNull(properties, "Nacos properties must not be null, you could put some keys from PropertyKeyConst");
this.groupId = groupId;
this.dataId = dataId;
this.properties = properties;
////////////////// 这里创建了一个监听器 ///////////////////
this.configListener = new Listener() {
@Override
public Executor getExecutor() {
return pool;
}
@Override
public void receiveConfigInfo(final String configInfo) {
RecordLog.info(String.format("[NacosDataSource] New property value received for (properties: %s) (dataId: %s, groupId: %s): %s",
properties, dataId, groupId, configInfo));
T newValue = NacosDataSource.this.parser.convert(configInfo);
// Update the new value to the property.
getProperty().updateValue(newValue);
}
};
initNacosListener();
loadInitialConfig();
}
private void initNacosListener() {
try {
this.configService = NacosFactory.createConfigService(this.properties);
// Add config listener.
configService.addListener(dataId, groupId, configListener);
} catch (Exception e) {
RecordLog.warn("[NacosDataSource] Error occurred when initializing Nacos data source", e);
e.printStackTrace();
}
}
通过代码可以知道,在构建 NacosDataSource 时会监听 /groupId/dataId 节点,既存储限流配置的节点,一旦数据有变化,就会通知客户端,从而更新 Sentinel 客户端的限流配置,进行了实时配置更新生效
# Nacos动态数据源实现方案
从官方文档中,引入动态数据源总共两个步骤,第一,将数据存储在 Nacos 集群中。第二,在客户端监听Nacos 从而实时生效。
# (一)将配置规则存储在 Nacos 中(改造控制台)
从 Sentinel 1.4.0 开始,Sentinel 控制台提供 DynamicRulePublisher 和 DynamicRuleProvider 接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。Sentinel 提供应用维度规则推送的示例页面(/v2/flow),用户改造控制台对接配置中心后可直接通过 v2 页面推送规则至配置中心(官方)
- 需要对sentinel-dashdoard 控制台进行改造:

- 将默认的实现方式调整为对接动态数据源

- 手动切换前端路由配置

- Nacos数据源配置,根据情况,修改Nacos配置中心地址

- Maven 编译重新打包
提取新的 sentinel-dashdoard.jar,该jar就是改造基于Nacos动态数据源的jar包。
# (二)将配置规则存储在 Nacos 中(不改造控制台)
在我们不该动控制台的前提下,是否还有其它的方式来集成Nacos动态数据源呢,答案是有的,理论上各datasource包应该既包含读又包含写,因为引入又不自动注册,但之前社区讨论过好像宿何觉得还是不加写数据源,那么自己画一个就行了,按照官方文档默认实现了本地文件,对应类 FileWritableDataSource,那是不是可以借鉴这个类的思路自行实现呢?可以的,我们一样可以通过 WritableDataSource 接口实现来实现动态数据源写入的操作类:NacosWritableDataSource。

接入方法如下:

# (三)配置规则从Nacos数据源读取
Nacos 数据源的实现类是 NacosDataSource
首先引入依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>x.y.z</version>
</dependency>
接入方法如下:

以上主要对接Nacos配置中心,实现数据的转换,并且监听配置中心的数据变化,当接收到数据变化后能够及时的将最新的规则更新到 RuleManager 中,实现限流配置持久化、实时动态感知配置中心变化。
# Sentinel与Nacos实现一写多读方式最终结果



