sentinel读写外部数据源

上一章我们学习了如何使用 Sentinel Dashboard 以及其底层通讯原理,其能帮我们在日常运维中很方便的进行规则管理。但还存在一个较大的缺陷,目前规则都存储在 dashboard 或业务系统的内存中,当发生重启时,之前维护的规则都会丢失,这对一个追求稳定运行的系统来说是万万不能接受的。所幸的是 sentinel 本身就支持规则的持久化存储以及从外部数据源中加载规则。

读取外部数据源

sentinel 可以从外部数据源加载规则,其支持多种主流的外部数据源,按照工作模式可以大致分为两种:

  • 推送模式(push base)的外部数据源
  • 拉取模式(pull base)的外部数据源

推送模式的外部数据源,指的是该数据源支持订阅模式,当数据源中的内容发生变化时会主动推送给 sentinel 进行规则变更。比较典型的有 zookeeper、apollo、nacose、redis 等。

拉取模式的外部数据源,指的是该数据源无法在内容发生变化时主动通知到 sentinel,只能由 sentinel 主动拉取内容进行比对,判断内容是否发生变化。比较典型的有 eureka、file(文件)等。

使用方式 & 实现原理

上面介绍了两种模式的外部数据源,那我们该怎么使用呢?sentinel 对这些数据源进行了封装,为我们屏蔽了这些数据源使用上的差异。我们可以通过以下方式来订阅数据源,读取其中的规则,并在后续发生内容变更时,及时的去更新 sentinel 规则。

// 我们以 zookeeper 为例,创建对应组件的可读数据源
// 第四个参数 Function<String, T> 用于将数据源中读取到的文本转换为规则
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new ZookeeperDataSource<>(remoteAddress, groupId, flowDataId,  
        source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));  
// 从数据源中获取 sentinel 相关配置,并且更新到对应的 RuleManager        
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

sentinel 底层是如何实现规则的初始化和增量加载的呢?阅读相关代码,我们可以发现其主要实际逻辑与 ReadableDataSource、SentinelProperty、PropertyListener 三个类有关,其类图如下:

在这里插入图片描述

  1. ReadableDataSource 提供 getProperty 方法获取相关 sentinel 配置
  2. SentinelProperty 提供注册 PropertyListener 来监听属性的变化
  3. PropertyListener 当属性发送变化时,更新对应 RuleManager 中的规则

在这里插入图片描述

此三个类的工作模式如上图。当数据源中的内容发生变化时,ReadableDataSource 会调用 SentinelProperty#updateValue 方法来触发相应的 PropertyListener 进行属性变更。通常情况下,PropertyListener 实现会在属性变更时同步更新对应 RuleManager 中的规则。那么 ReadableDataSource 是如何感知外部数据源内容的变化呢?下面我们将分别了解推送模式和拉取模式数据源的具体实现。

推送模式的数据源实现

push 模式主要适用于支持订阅模式的数据源,典型的数据源有: zookeeper、apollo、nacos 等。我们以 zookeeper 为例,介绍对应的 push 模式的 ReadableDataSource 实现。

在 zookeeper 中我们可以订阅节点,在节点内容发生变化时,执行对应回调方法,ZookeeperDataSource 就是通过该特性实现相关属性值变化时的感知。相关源码解析如下:

public ZookeeperDataSource(final String serverAddr, final List<AuthInfo> authInfos, final String groupId, final String dataId,
                           Converter<String, T> parser) {
    // ....
    init(serverAddr, authInfos);
}

private void init(final String serverAddr, final List<AuthInfo> authInfos) {
	  // 注册结点监听器
    initZookeeperListener(serverAddr, authInfos);
    // 加载初始化规则
    loadInitialConfig();
}

private void loadInitialConfig() {
    try {
	    // 获取节点内容,并通过自定义的 Convert 转换为规则
        T newValue = loadConfig();
        if (newValue == null) {
            RecordLog.warn("[ZookeeperDataSource] WARN: initial config is null, you may have to check your data source");
        }
        // 更新 SentinelProperty 值
        getProperty().updateValue(newValue);
    } catch (Exception ex) {
        RecordLog.warn("[ZookeeperDataSource] Error when loading initial config", ex);
    }
}

private void initZookeeperListener(final String serverAddr, final List<AuthInfo> authInfos) {
    try {

        this.listener = CuratorCacheListener.builder().forNodeCache(() -> {
            try {
                T newValue = loadConfig();
                RecordLog.info("[ZookeeperDataSource] New property value received for ({}, {}): {}",
                        serverAddr, path, newValue);
                // Update the new value to the property.
                getProperty().updateValue(newValue);
            } catch (Exception ex) {
                RecordLog.warn("[ZookeeperDataSource] loadConfig exception", ex);
            }
        }).build();

        // 创建 zk 连接,并注册监听器
        // ...
    } catch (Exception e) {
        RecordLog.warn("[ZookeeperDataSource] Error occurred when initializing Zookeeper data source", e);
        e.printStackTrace();
    }
}

拉取模式的数据源实现

一些数据源并不支持发订阅的模式,这个时候数据源中的内容发生变化后,该如何感知到呢?

既然 sentinel 不能被动收到通知,那就主动拉取。主动拉取的实现一般是通过定时访问数据源内容,判断是否发生变化。仅支持 Pull 模式的数据源有 file、eureka,我们以 file 为例,介绍对应的 pull 模式的 ReadableDataSource 实现。相关源码解析如下:

public FileRefreshableDataSource(File file, Converter<String, T> configParser, long recommendRefreshMs, int bufSize,
                                 Charset charset) throws FileNotFoundException {
    // 参数验证和赋值
    // If the file does not exist, the last modified will be 0.
    this.lastModified = file.lastModified();
    // 加载初始化规则
    firstLoad();
}

private void firstLoad() {
    try {
        T newValue = loadConfig();
        getProperty().updateValue(newValue);
    } catch (Throwable e) {
        RecordLog.info("loadConfig exception", e);
    }
}

// 此方法实际由定时任务触发
@Override
public String readSource() throws Exception {
    if (!file.exists()) {
        // Will throw FileNotFoundException later.
        RecordLog.warn(String.format("[FileRefreshableDataSource] File does not exist: %s", file.getAbsolutePath()));
    }
    FileInputStream inputStream = null;
    try {
        inputStream = new FileInputStream(file);
        FileChannel channel = inputStream.getChannel();
        if (channel.size() > buf.length) {
            throw new IllegalStateException(file.getAbsolutePath() + " file size=" + channel.size()
                + ", is bigger than bufSize=" + buf.length + ". Can't read");
        }
        int len = inputStream.read(buf);
        return new String(buf, 0, len, charset);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (Exception ignore) {
            }
        }
    }
}

@Override
protected boolean isModified() {
    long curLastModified = file.lastModified();
    if (curLastModified != this.lastModified) {
        this.lastModified = curLastModified;
        return true;
    }
    return false;
}

规则的持久化

上面我们了解了 sentinel 如何初始和增量的获取外部数据源中的规则内容,那么 sentinel 如何在运行时当规则发生变化时对其持久化的呢?

sentinel 中定义了 ReadableDataSource 抽象类用于抽象外部数据源内容的读取操作,相应的也定义了 WritableDataSource 抽象类用于抽象规则变化时的持久化操作。该接口定义非常简单,只有两个方法:

  • write 将规则写入数据源中
  • close 用于数据源的关闭

其使用方式也非常的简单:

// 具体写法请参考:sentinel-demo/sentinel-demo-dynamic-file-rule
// 创建写入数据源的实现
WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, Objects::toString);
// 将其注册到 WritableDataSourceRegistry 中的对应规则(如:FlowRule、SystemRule)数据源中
// 当相关规则变更时,会触发 WritableDataSource#write 操作
WritableDataSourceRegistry.registerFlowDataSource(wds);

当数据源被注册以后,其什么时候会触发规则的写入呢?我们查看 WritableDataSource#write 方法的调用链路可以发现,其在以下几个规则变更时会被调用:

  • 一般规则(限流规则、授权规则、系统规则等)变更时
  • 热点规则变更时
  • 网关路由分组变更时
  • 网关限流规则变更时

在这里插入图片描述

总结

在本章我们了解了 sentinel 是如何从外部数据源加载规则,以及如何在规则发生变更时写入外部数据源进行持久化的。

sentinel 支持从大部分主流的外部数据源加载规则,按照工作模式大致可以分为两种:

  • 推送模式的数据源,此类型数据源支持订阅模式,当数据内容发生变化时,主动推送到 sentinel 进行对应的规则更新
  • 拉取模式的数据源,此类型数据不支持订阅模式,由 sentinel 定时去拉取内容,判断内容是否变化,从而更新对应的规则

sentinel 会在以下几种规则发生变更时触发已注册的数据源写入动作:

  • 一般规则(限流规则、授权规则、系统规则等)变更时
  • 热点规则变更时
  • 网关路由分组变更时
  • 网关限流规则变更时
  • 37
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值