龙空技术网

第9章:Sentinel规则持久化,让你的系统规则永不失效!

Seven的代码实验室 2518

前言:

今天朋友们对“sentinel持久化问题”大致比较珍视,兄弟们都想要分析一些“sentinel持久化问题”的相关资讯。那么小编也在网摘上搜集了一些有关“sentinel持久化问题””的相关内容,希望我们能喜欢,兄弟们快快来了解一下吧!

#头条创作挑战赛#

终身学习、乐于分享、共同成长!

前言

在前两章中,我们设定的规则(无论是通过代码还是控制台)都只是保存在内存中的,不能在应用重启之后保留。如果规则丢失,我们需要重新设置它们,这非常麻烦。

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

原理如下:

试想一下,客户端连接上Dashboard之后,我们在Dashboard上为客户端配置好了规则,并推送给了客户端。这时由于一些因素客户端出现异常,服务不可用了,当客户端恢复正常再次连接上Dashboard后,这时所有的规则都丢失了,我们还需要重新配置一遍规则,这也太不友好了。

Sentinel本身没有提供规则持久化的功能,因此我们需要自己实现。在本章中,我们将介绍如何实现规则持久化。

规则持久化实现原理

实现规则持久化的思路很简单,就是将保存在RuleManager内存中的规则持久化一份副本到存储介质(可以是Nacos、Zookeeper、MySQL、Redis、File)中,当应用重启后再从存储介质中把规则load到内存,这样样规则就不会丢失了。

如下图:

Sentinel提供了两个接口来实现规则的持久化:ReadableDataSourceWritableDataSource

WritableDataSource是用于在客户端扩展写数据源,客户端主动向某个规则中心定期轮询拉取规则,这个规则中心可以DB、文件等,对客户端有一定的入侵性,所以不太推荐这种方式。ReadableDataSource用于在SentinelDashboard扩展读数据源,由规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用Nacos、Zookeeper等配置中心,这种方式有更好的实时性和一致性保证,生产环境一般采用这种方式实现。

看一下ReadableDataSource接口的具体的定义:

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

接口还是比较简单的,只有四个方法,另外 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 {        T value = parser.convert(conf);        return value;    }    @Override    public SentinelProperty<T> getProperty() {        return property;    }}

每个具体的DataSource实现类需要做三件事:

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

原理如下图:

几个注意点:

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

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

#规则推送的三种模式

推送模式

说明

优点

缺点

原始模式

API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource)

简单,无任何依赖

不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境

Pull 模式

扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等

简单,无任何依赖;规则持久化

不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。

Push 模式

扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。

规则持久化;一致性;快速

引入第三方依赖

原始模式

如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中。这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。

拉模式

pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的WritableDataSourceRegistry 中。

推模式

生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。

因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台配置中心Sentinel 数据源Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了。

#应用推模式基于Nacos配置中心实现规则持久化

Sentinel支持五种持久化方式,这里以Nacos为例进行演示。虽然Sentinel源码中默认提供了这些方式的实现,但并未默认启用,需手动修改源码以启用支持。

Sentinel源码(opens new window)

把源码下载下来到本地导入IDEA

接下来进行源码改造,步骤如下:

sentinel-dashboard工程下的pom.xml文件

sentinel-datasource-nacos依赖的<scope>test</scope>这一行注释掉

<!-- for Nacos rule publisher sample --><dependency>    <groupId>com.alibaba.csp</groupId>    <artifactId>sentinel-datasource-nacos</artifactId></dependency>
修改相关类

将整个sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos目录

拷贝到sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos目录

修改NacosConfig类

修改为实际的Nacos地址。

这里以流控规则的持久化为例,修改FlowControllerV1类,其他规则类似

com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1修改的地方如下:

注入我们定义好的provider和publisher,即对应的FlowRuleNacosProviderFlowRuleNacosPublisher

    @Autowired    @Qualifier("flowRuleNacosProvider")    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;    @Autowired    @Qualifier("flowRuleNacosPublisher")    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
修改rules接口,改为从nacos中读取数据。
List<FlowRuleEntity> rules = ruleProvider.getRules(app);if (rules != null && !rules.isEmpty()) {    for (FlowRuleEntity entity : rules) {        entity.setApp(app);        if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {            entity.setId(entity.getClusterConfig().getFlowId());        }    }}rules = repository.saveAll(rules);return Result.ofSuccess(rules);
最后修改publishRules方法
    private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {//        List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));//        return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);        List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));        try {            rulePublisher.publish(app, rules);            logger.info("添加限流规则成功.....");        } catch (Exception e) {            e.printStackTrace();            logger.info("添加限流规则失败.....");        }        return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);    }

至此,源码改造就完成了,我们来启动一下DashboardApplication,启动正常就可以了。

接下来配置客户端,客户端项目使用前两张中使用到springcloudalibaba-step-07项目作为演示

bootstrap.yml配置文件中添加如下配置即可

      datasource:        flow-rules: ## 流控规则          nacos:            server-addr: 127.0.0.1:8848            data-id: ${spring.application.name}-flow-rules            group-id: SENTINEL_GROUP            data-type: json            rule-type: flow

接下来访问Sentinel控制台进行流控规则设置,首次进来是没有客户端的,我们需要访问一次客户端应用的接口,请求一次之后就可以看到客户端了。

我们设置一个流控规则

新增完成后查看Nacos配置列表,这时会看到新增了一个Data Idsentinel-demo-flow-rules的配置,这个dataid的默认命名规则是{客户端应用名}-flow-rules,当然也可修改代码来自定义这个命名规则。

测试验证一下这个规则是否有效,通过postman快速点击访问接口,1秒访问多次,这时候会出现失败。

至此,Sentinel规则持久化实现完成!

其他规则的实现,比如熔断规则、热点规则、系统规则等都可以参照这个流控规则这个方式来实现,总结下来核心就是providerpublisher,比如熔断规则的持久化实现则新增对应的DegradeRuleNacosProviderDegradeRuleNacosProvider,实现方式参照流控规则对应provider与publisher的实现。

对于所有类型规则的持久化实现也可以关注我的公众号Seven的代码实验室回复Sentinel获取完整源码。

总结本身分析了Sentinel持久化的实现原理,思路很简单,就是将保存在RuleManager内存中的规则持久化一份副本到存储介质,当应用重启后再从存储介质中把规则load到内存。需要依赖Sentinel提供的两个接口ReadableDataSourceWritableDataSourceSentinel规则推动的三种模式:原始模式、拉模式、推模式,生产环境推荐使用推模式。应用推模式基于Nacos配置中心实现了Sentinel的规则持久化。

标签: #sentinel持久化问题