Sentinel 实战-规则持久化

逅弈 转载请注明原创出处,谢谢!java

系列文章

Sentinel 原理-全解析redis

Sentinel 原理-调用链ide

Sentinel 原理-滑动窗口post

Sentinel 原理-实体类ui

Sentinel 实战-限流篇this

Sentinel 实战-控制台篇编码

规则持久化的5种方式

规则丢失

不管是经过硬编码的方式来更新规则,仍是经过接入 Sentinel Dashboard 后,在页面上操做来更新规则,都没法避免一个问题,那就是服务从新后,规则就丢失了,由于默认状况下规则是保存在内存中的。spa

Dashboard 是经过 transport 模块来获取每一个 Sentinel 客户端中的规则的,获取到的规则经过 RuleRepository 接口保存在 Dashboard 的内存中,若是在 Dashboard 页面中更改了某个规则,也会调用 transport 模块提供的接口将规则更新到客户端中去。3d

试想这样一种状况,客户端链接上 Dashboard 以后,咱们在 Dashboard 上为客户端配置好了规则,并推送给了客户端。这时因为一些因素客户端出现异常,服务不可用了,当客户端恢复正常再次链接上 Dashboard 后,这时全部的规则都丢失了,咱们还须要从新配置一遍规则,这确定不是咱们想要的。code

rules-in-memory.png

如上图所示,当 Sentinel 的客户端挂掉以后,保存在各个 RuleManager 中的规则都会付之一炬,因此在生产中是绝对不能这么作的。

规则持久化原理

那咱们有什么办法能解决这个问题呢,其实很简单,那就是把本来保存在 RuleManager 内存中的规则,持久化一份副本出去。这样下次客户端重启后,能够从持久化的副本中把数据 load 进内存中,这样就不会丢失规则了,以下图所示:

rules-in-persistence.png

Sentinel 为咱们提供了两个接口来实现规则的持久化,他们分别是:ReadableDataSource 和 WritableDataSource。

其中 WritableDataSource 不是咱们本次关心的重点,或者说 WritableDataSource 并无那么重要,由于一般各类持久化的数据源已经提供了具体的将数据持久化的方法了,咱们只须要把数据从持久化的数据源中获取出来,转成咱们须要的格式就能够了。

下面咱们来看一下 ReadableDataSource 接口的具体的定义:

public interface ReadableDataSource<S, T> {
	// 从数据源中读取原始的数据
    S readSource() throws Exception;
	// 将原始数据转换成咱们所需的格式
    T loadConfig() throws Exception;
    // 获取该种数据源的SentinelProperty对象
    SentinelProperty<T> getProperty();
}
复制代码

接口很简单,最重要的就是这三个方法,另外 Sentinel 还为咱们提供了一个抽象类:AbstractDataSource,该抽象类中实现了两个方法,具体的数据源实现类只须要实现一个 readSource 方法便可,具体的代码以下:

public abstract class AbstractDataSource<S, T> implements ReadableDataSource<S, T> {
	// Converter接口负责转换数据
    protected final Converter<S, T> parser;
    // SentinelProperty接口负责触发PropertyListener
    // 的configUpdate方法的回调
    protected final SentinelProperty<T> property;

    public AbstractDataSource(Converter<S, T> parser) {
        if (parser == null) {
            throw new IllegalArgumentException("parser can't be null");
        }
        this.parser = parser;
        this.property = new DynamicSentinelProperty<T>();
    }
    @Override
    public T loadConfig() throws Exception {
        return loadConfig(readSource());
    }
    public T loadConfig(S conf) throws Exception {
        return parser.convert(conf);
    }
    @Override
    public SentinelProperty<T> getProperty() {
        return property;
    }
}
复制代码

实际上每一个具体的 DataSource 实现类须要作三件事:

  • 实现 readSource 方法将数据源中的原始数据转换成咱们能够处理的数据S
  • 提供一个 Converter 来将数据S转换成最终的数据T
  • 将最终的数据T更新到具体的 RuleManager 中去

我把规则是如何从数据源加载进 RuleManager 中去的完整流程浓缩成了下面这张图:

rules-transfer.png

你们能够就着这张图对照着源码来看,能够很容易的弄明白这个过程,这里我就再也不展开具体的源码讲了,有几点须要注意的是:

  • 规则的持久化配置中心能够是redis、nacos、zk、file等等任何能够持久化的数据源,只要能保证更新规则时,客户端能获得通知便可
  • 规则的更新能够经过 Sentinel Dashboard 也能够经过各个配置中心本身的更新接口来操做
  • AbstractDataSource 中的 SentinelProperty 持有了一个 PropertyListener 接口,最终更新 RuleManager 中的规则是 PropertyListener 去作的

规则持久化

好了,知道了具体的原理了,下面咱们就来说解下如何来接入规则的持久化。

目前 Sentinel 中默认实现了5种规则持久化的方式,分别是:file、redis、nacos、zk和apollo。

下面咱们对这5种方式一一进行了解,以持久化限流的规则为例。

File

文件持久化有一个问题就是文件不像其余的配置中心,数据发生变动后会发出通知,使用文件来持久化的话就须要咱们本身定时去扫描文件,来肯定文件是否发现了变动。

文件数据源是经过 FileRefreshableDataSource 类来实现的,他是经过文件的最后更新时间来判断规则是否发生变动的。

首先须要引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-extension</artifactId>
    <version>x.y.z</version>
</dependency>
复制代码

接入的方法以下:

private void init() throws Exception {
	// 保存了限流规则的文件的地址
	String flowRuleName = yourFlowRuleFileName();
	Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
    // 建立文件规则数据源
    FileRefreshableDataSource<List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>(flowRuleName, parser);
    // 将Property注册到 RuleManager 中去
    FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
复制代码

PS:须要注意的是,咱们须要在系统启动的时候调用该数据源注册的方法,不然不会生效的。具体的方式有不少,能够借助 Spring 来初始化该方法,也能够自定义一个类来实现 Sentinel 中的 InitFunc 接口来完成初始化。

Sentinel 会在系统启动的时候经过 spi 来扫描 InitFunc 的实现类,并执行 InitFunc 的 init 方法,因此这也是一种可行的方法,若是咱们的系统没有使用 Spring 的话,能够尝试这种方式。

Redis

Redis 数据源的实现类是 RedisDataSource。

首先引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-redis</artifactId>
    <version>x.y.z</version>
</dependency>
复制代码

接入方法以下:

private void init() throws Exception {
	String redisHost = yourRedisHost();
	String redisPort = yourRedisPort();
    String ruleKey = yourRuleKey();
    String channel = yourChannel();
	Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
    RedisConnectionConfig config = RedisConnectionConfig.builder()
        .withHost(redisHost)
        .withPort(redisPort)
        .build();
    ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<>(config, ruleKey, channel, parser);
    FlowRuleManager.register2Property(redisDataSource.getProperty());
}
复制代码

Nacos

Nacos 数据源的实现类是 NacosDataSource。

首先引入依赖:

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

接入方法以下:

private void init() throws Exception {
	String remoteAddress = yourRemoteAddress();
	String groupId = yourGroupId();
    String dataId = yourDataId();
	Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
    ReadableDataSource<String, List<FlowRule>> nacosDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser);
    FlowRuleManager.register2Property(nacosDataSource.getProperty());
}
复制代码

Zk

Zk 数据源的实现类是 ZookeeperDataSource。

首先引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-zookeeper</artifactId>
    <version>x.y.z</version>
</dependency>
复制代码

接入方法以下:

private void init() throws Exception {
	String remoteAddress = yourRemoteAddress();
	String path = yourPath();
	Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
    ReadableDataSource<String, List<FlowRule>> zookeeperDataSource = new ZookeeperDataSource<>(remoteAddress, path, parser);
    FlowRuleManager.register2Property(zookeeperDataSource.getProperty());
}
复制代码

Apollo

Apollo 数据源的实现类是 ApolloDataSource。

首先引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-apollo</artifactId>
    <version>x.y.z</version>
</dependency>
复制代码

接入方法以下:

private void init() throws Exception {
	String namespaceName = yourNamespaceName();
	String ruleKey = yourRuleKey();
    String defaultRules = yourDefaultRules();
	Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
    ReadableDataSource<String, List<FlowRule>> apolloDataSource = new ApolloDataSource<>(namespaceName, ruleKey, path, defaultRules);
    FlowRuleManager.register2Property(apolloDataSource.getProperty());
}
复制代码

能够看到5中持久化的方式基本上大同小异,主要仍是对接每种配置中心,实现数据的转换,而且监听配置中心的数据变化,当接收到数据变化后可以及时的将最新的规则更新到 RuleManager 中去就能够了。

更多原创好文,请关注「逅弈逐码」

更多原创好文,请关注公众号「逅弈逐码」

相关文章
相关标签/搜索