# Sentinel与Nacos实现动态数据源

# 介绍

按照官方的文档定义,Sentinel是一个分布式系统的流量防卫兵,是面向分布式服务架构的流量控制组件,主要提供限流、熔断等服务治理的相关的功能,来保障微服务的稳定性。

# 限流与熔断

注意

看到了这里,你可能会对限流、熔断还不知道是什么概念,有必要知道一下, 那什么是限流和熔断呢?我对限流、熔断的一些自我理解,给大家分享一下:

限流:限流还是比较好理解的,比如一个项目的上线之前,经过了性能测试评估,通常使用TPS(系统吞吐量)对流量的来进行一种描述,比如服务在 TPS 到达性能测试评估的阈值 1w/s 事,系统的资料利用率飘升,与此同时响应时间也急剧增大,为了保障服务的稳定性,有必要控制单位时间的请求量,可以采用拒绝、排队等策略,实现流量的削峰填谷,这就是限流为什么愈发重要的原因。

熔断:当系统中某一个服务因为某种原因突然变得不可用或响应过慢,例如某个线程池卡住了,导致发送到它上面的请求会出现超时而报错,这样整个系统的大部分请求出现超时报错,影响了整个系统的可用性,因此对这个服务的调用进行快速失败,不再继续调用目标服务,直接返回,快速释放资源,来保证整个系统服务的可用性,避免造成其它的服务连锁反应,这就是自我理解的熔断机制。

有了上面的基本认识,接下来会进行Sentinel在生产中控制台改造和实现动态数据源绑定来做一下分享。

# Sentinel设计理念

Sentinel 中主要的有如下几个角色:控制台、规则数据源、Sentinel 客户端。

Sentinel 的角色之间的关系图:

Sentinel的角色之间的关系图

# Sentinel改造思路

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

Sentinel的思路

# (一)Sentinel改造思路方向

从官方提供的demo代码如下图:

Sentinel

官方的 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 控制台提供 DynamicRulePublisherDynamicRuleProvider 接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。Sentinel 提供应用维度规则推送的示例页面(/v2/flow),用户改造控制台对接配置中心后可直接通过 v2 页面推送规则至配置中心(官方)

  1. 需要对sentinel-dashdoard 控制台进行改造:

Sentinel

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

Sentinel

  1. 手动切换前端路由配置

Sentinel

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

Sentinel

  1. Maven 编译重新打包

提取新的 sentinel-dashdoard.jar,该jar就是改造基于Nacos动态数据源的jar包。

# (二)将配置规则存储在 Nacos 中(不改造控制台)

在我们不该动控制台的前提下,是否还有其它的方式来集成Nacos动态数据源呢,答案是有的,理论上各datasource包应该既包含读又包含写,因为引入又不自动注册,但之前社区讨论过好像宿何觉得还是不加写数据源,那么自己画一个就行了,按照官方文档默认实现了本地文件,对应类 FileWritableDataSource,那是不是可以借鉴这个类的思路自行实现呢?可以的,我们一样可以通过 WritableDataSource 接口实现来实现动态数据源写入的操作类:NacosWritableDataSource

Sentinel

接入方法如下:

Sentinel

# (三)配置规则从Nacos数据源读取

Nacos 数据源的实现类是 NacosDataSource

首先引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>x.y.z</version>
</dependency>

接入方法如下:

Sentinel

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

# Sentinel与Nacos实现一写多读方式最终结果

Sentinel

Sentinel

Sentinel

Sentinel

上次更新: 2020-07-04 19:04:20