SpringCloud Alibaba Sentinel规则持久化

简介: SpringCloud Alibaba Sentinel规则持久化

Sentinel使用及规则配置中,介绍了常见的规则配置方式,但是通过 Sentinel Dashboard配置的规则是存在内存中的,并且不能推送到本地文件或Nacos中,如果客户端重启那么规则都会丢失。所以需要一种方式,将规则进持久化。

回顾一下,规则的推送存在3种模式,原始模式下规则直接被推送到内存,无法持久化,看一下其余两种模式:

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

Push模式:扩展读数据源(ReadalbeDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用NacosZookeeper等配置中心

下面分别对这两种模式进行扩展说明。

image.png

Pull模式下,首先 Sentinel Dashboard通过api将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。首先添加依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-extension</artifactId>
</dependency>

参考上篇文章中第二种方式的使用本地文件配置数据源,进行项目的改造。我们需要的就是将规则写回到文件中。分3步看一下原理:

1、FileRefreshableDataSource定时从指定文件中读取规则json文件,这里我们读取项目目录下的本地文件,如果发现文件发生变化,就更新规则缓存

ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
        flowRulePath,flowRuleListParser);

2、将可读数据源注册至FlowRuleManager,这样当规则文件发生变化时,就会更新规则到内存

FlowRuleManager.register2Property(flowRuleRDS.getProperty());

3、将可写数据源注册WritableDataSourceRegistry中,这样收到Dashboard推送的规则时,Sentinel会先更新到内存,然后将规则写入到json文件中

WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
     flowRulePath,this::encodeJson);
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

完整代码如下:

public class FileDataSourceInit implements InitFunc {
    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<FlowRule>>() {});
    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<DegradeRule>>() {});
    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<SystemRule>>() {});
    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<AuthorityRule>>() {});
    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<ParamFlowRule>>() {});
    @Override
    public void init() throws Exception {
        String prefix = new File("sentinel-persist-file/src/main/resources").getAbsolutePath();
        String ruleDir = prefix+"/rules" ;
        String flowRulePath = ruleDir + "/flowRule.json";
        String degradeRulePath = ruleDir + "/degradeRule.json";
        String systemRulePath = ruleDir + "/systemRule.json";
        String authorityRulePath = ruleDir + "/authorityRule.json";
        String paramFlowRulePath = ruleDir + "/paramFlowRule.json";
        this.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(paramFlowRulePath);
        // 流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );   
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
                degradeRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
        // 系统规则
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
        // 授权规则
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
                authorityRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
        // 热点参数规则
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
                paramFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }
    private void mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }
    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }
    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}

项目启动后,会自动创建5个json文件用于存放不同的持久化规则:

image.png

添加一条流控规则进行测试:

image.png

查看项目目录下的flowRule.json文件:

image.png

这样,基于Pull模式的持久化改造就完成了,但是该模式下存在以下缺点:

因为是基于定时任务的轮询方式,可能存在间隔时间太长,造成存在延迟的情况

规则存储在本地文件中,如果需要项目迁移,需要同时将多个规则文件迁移,否则会出现规则的丢失。

image.png

使用Push模式能够在Sentinel Dashboard中,将规则推送到nacos或其他远程配置中心。Sentinel客户端链接nacos,获取规则配置,并监听nacos配置变化,如发生变化,就更新本地缓存,从而让本地缓存总是和nacos一致。

使用上篇文章中的使用nacos配置数据源项目进行改造,客户端不需要添加额外依赖。修改application.yml配置文件:

spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: 127.0.0.1:8848
            group-id: SENTINEL_GROUP
            rule-type: flow
            data-id: ${spring.application.name}-flow-rules
            data-type: json
        degrade:
          nacos:
            server-addr: 127.0.0.1:8848
            group-id: SENTINEL_GROUP
            rule-type: degrade
            data-id: ${spring.application.name}-degrade-rules
            data-type: json

这里针对流控和降级分别定义了它们与nacos中规则配置文件的映射关系,通过group-iddata-id指定。

在该模式下,需要对sentinel dashboard进行一下改造,使其能向nacos推送规则。这里我采用的方式是下载sentinel 1.8的源码后,将sentinel-dashboardmodule导入到我们自己的项目中,避免了对整个sentinel项目的过多依赖,方便独立启动。

导入后需要导入一些其原先父pom中的依赖,除此外,还需要修改pom.xml文件:

<!-- for Nacos rule publisher sample -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <scope>test</scope>
</dependency>

需要将<scope>一行注释掉:

<!--<scope>test</scope>-->

添加nacos-api的依赖:

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-api</artifactId>
    <version>1.2.1</version>
</dependency>

找到目录:

/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos

将整个目录拷贝到 :

/src/main/java/com/alibaba/csp/sentinel/dashboard/rule/nacos

image.png

这里刚拷过来的时候是没有前两个类的,只有后面4个类,前两个类后面会讲怎么实现。拷贝目录主要是为了使用其中的两个类:

FlowRuleNacosProvider:实现了DynamicRuleProvider接口,用于从nacos上读取配置

FlowRuleNacosPublisher:实现了DynamicRulePublisher接口,用于将规则推送到nacos

修改NacosConfig类,这里需要把nacos的地址改为实际地址,注意不同版本的sentinel的源码在这里修改时可能会稍有不同,有的小伙伴如果启动时报错,在地址后面加上nacos的端口号就可以:

@Bean
public ConfigService nacosConfigService() throws Exception {
    return ConfigFactory.createConfigService("localhost");
}

修改流控规则FlowControllerV1,添加下面代码,使用@Qualifier注解注入我们刚才复制来的 PublisherProvider

@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

修改apiQueryMachineRules方法,使用provider读取规则:

@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app) {
    try {
        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);
    } catch (Throwable throwable) {
        logger.error("Error when querying flow rules", throwable);
        return Result.ofThrowable(-1, throwable);
    }
}

修改publishRules方法,推送规则到nacos

private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
    List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
    try {
        rulePublisher.publish(app, rules);         
    } catch (Exception e) {
        e.printStackTrace();
    }
    return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
}

到这对流控规则的持久化就完成了,下面进行测试,首先在Sentinel Dashboard添加流控规则:

image.png

查看nacos,已经自动创建了一条对应data-id的流控规则:

image.png

接下来直接在nacos上修改流控规则,将QPS限制改为30:

image.png

刷新Sentinel Dashboard,控制台上的显示规则也已经被修改过了:

image.png

官方test目录下只对流控规则进行了扩展,接下来我们模仿官方的写法对降级规则进行持久化。复制FlowRuleNacosProvider,命名为DegradeRuleNacosProvider,将其中所有的FlowRuleEntity改为DegradeRuleEntity,再更改配置中的data-id,修改完成后如下:

@Component("degradeRuleNacosProvider")
public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {
    @Autowired
    private ConfigService configService;
    @Autowired
    private Converter<String, List<DegradeRuleEntity>> converter;
    @Override
    public List<DegradeRuleEntity> getRules(String appName) throws Exception {
        String rules = configService.getConfig(appName + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
            NacosConfigUtil.GROUP_ID, 3000);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }
}

同样,复制FlowRuleNacosPublisher作为DegradeRuleNacosPublisher,改动方式与上面相同:

@Component("degradeRuleNacosPublisher")
public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {
    @Autowired
    private ConfigService configService;
    @Autowired
    private Converter<List<DegradeRuleEntity>, String> converter;
    @Override
    public void publish(String app, List<DegradeRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return;
        }
        configService.publishConfig(app + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
            NacosConfigUtil.GROUP_ID, converter.convert(rules));
    }
}

修改DegradeController,注入创建的providerpublisher

@Autowired
@Qualifier("degradeRuleNacosProvider")
private DynamicRuleProvider<List<DegradeRuleEntity>> provider;
@Autowired
@Qualifier("degradeRuleNacosPublisher")
private DynamicRulePublisher<List<DegradeRuleEntity>> publisher;

测试中发现,可以不用修改降级中的apiQueryMachineRules方法,只修改publishRules方法就可以发布或修改新的规则到nacos

private boolean publishRules(String app, String ip, Integer port) {
    List<DegradeRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
    try {
        publisher.publish(app, rules);        
    } catch (Exception e) {
        e.printStackTrace();        
    }
    return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules);
}

以此类推,还可以添加对系统保护、黑白名单以及热点参数的规则持久化,这样对Sentinel Dashboard的改造就基本完成了,修改规则时就会被同步到nacos中,并且在重启dashboardnacos以及业务项目的客户端后,规则仍然存在。相对于Pull模式下的持久化,Push模式具有强一致性,并且拥有更高的性能,但是相应的需要对Sentinel Dashboard进行源码的改造,具有一定的复杂性。

相关文章
|
3月前
|
存储 NoSQL Redis
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
Redis持久化、RDB和AOF方案、Redis主从集群、哨兵、分片集群、散列插槽、自动手动故障转移
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
|
5月前
|
监控 算法 Java
高并发架构设计三大利器:缓存、限流和降级问题之配置Sentinel的流量控制规则问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之配置Sentinel的流量控制规则问题如何解决
|
7月前
|
Java 测试技术 Nacos
|
7月前
|
Java API Nacos
第十二章 Spring Cloud Alibaba Sentinel
第十二章 Spring Cloud Alibaba Sentinel
297 0
|
7月前
|
SpringCloudAlibaba 监控 Java
SpringCloud Alibaba微服务-- Sentinel的使用(保姆级)
SpringCloud Alibaba微服务-- Sentinel的使用(保姆级)
|
7月前
|
Java Nacos Sentinel
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(九)Nacos+Sentinel+Seata
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(九)Nacos+Sentinel+Seata
887 0
|
7月前
|
SpringCloudAlibaba 监控 Java
SpringCloud Alibaba Sentinel实现熔断与限流--学习笔记
SpringCloud Alibaba Sentinel实现熔断与限流--学习笔记
112 0
|
4月前
|
Java UED Sentinel
微服务守护神:Spring Cloud Sentinel,让你的系统在流量洪峰中稳如磐石!
【8月更文挑战第29天】Spring Cloud Sentinel结合了阿里巴巴Sentinel的流控、降级、熔断和热点规则等特性,为微服务架构下的应用提供了一套完整的流量控制解决方案。它能够有效应对突发流量,保护服务稳定性,避免雪崩效应,确保系统在高并发下健康运行。通过简单的配置和注解即可实现高效流量控制,适用于高并发场景、依赖服务不稳定及资源保护等多种情况,显著提升系统健壮性和用户体验。
93 1
|
6月前
|
监控 Java Sentinel
使用Sentinel进行服务调用的熔断和限流管理(SpringCloud2023实战)
Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
166 3
|
2月前
|
负载均衡 算法 Java
蚂蚁面试:Nacos、Sentinel了解吗?Springcloud 核心底层原理,你知道多少?
40岁老架构师尼恩分享了关于SpringCloud核心组件的底层原理,特别是针对蚂蚁集团面试中常见的面试题进行了详细解析。内容涵盖了Nacos注册中心的AP/CP模式、Distro和Raft分布式协议、Sentinel的高可用组件、负载均衡组件的实现原理等。尼恩强调了系统化学习的重要性,推荐了《尼恩Java面试宝典PDF》等资料,帮助读者更好地准备面试,提高技术实力,最终实现“offer自由”。更多技术资料和指导,可关注公众号【技术自由圈】获取。
蚂蚁面试:Nacos、Sentinel了解吗?Springcloud 核心底层原理,你知道多少?