Spring Cloud Alibaba——Sentinel规则持久化

一、在生产环境中使用Sentinel

生产环境的Sentinel Dashboard需要具备下面几个特性:

  • 1、规则管理及推送,集中管理和推送规则。
  • 2、监控,支持可靠、快速的实时监控和历史监控数据查询。
  • 3、权限控制,区分用户角色,来进行操作。

1、规则管理及推送

一般来说,规则的推送有下面三种模式:

推送模式 说明 优点 缺点
原始模式 API 将规则推送至客户端并直接更新到内存中,扩展写数据源 简单,无任何依赖 不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
Pull 模式 扩展写数据源, 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件等 简单,无任何依赖;规则持久化 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
Push 模式 扩展读数据源ReadableDataSource,规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。 规则持久化;一致性;快速 引入第三方依赖

2、动态规则扩展

Sentinel的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。

Sentinel提供两种方式修改规则:

  • 1、通过API直接修改(loadRules)——手动修改。
  • 2、通过 DataSource 适配不同数据源修改——动态规则。

手动通过API修改比较直观,可以通过以下几个API修改不同的规则:

FlowRuleManager.loadRules(List rules);  // 修改流控规则

DegradeRuleManager.loadRules(List rules);  // 修改降级规则

手动修改规则(硬编码方式)一般仅用于测试和演示,生产上一般通过动态规则源的方式来动态管理规则。示例代码如下所示:

//TODO 发布限流规则方式一:手动通过API修改
final String rule = "[\n"
        + "  {\n"
        + "    \"resource\": \"login\",\n"
        + "    \"controlBehavior\": 0,\n"
        + "    \"count\": 5.0,\n"
        + "    \"grade\": 1,\n"
        + "    \"limitApp\": \"default\",\n"
        + "    \"strategy\": 0\n"
        + "  }\n"
        + "]";

ConfigService configService = NacosFactory.createConfigService(serverAddr);
boolean isPublishOk = configService.publishConfig(dataId, group, rule);
System.out.println(isPublishOk);

上述 loadRules() 方法只接受内存态的规则对象,但更多时候规则存储在文件、数据库或者配置中心当中。DataSource 接口给我们提供了对接任意配置源的能力。相比直接通过API 修改规则,实现 DataSource 接口是更加可靠的做法。

我们推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource 接口端监听规则中心实时获取变更,流程如下:


原始模式

如果不做任何修改,Dashboard的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:

这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。

二、DataSource扩展方式介绍

1、DataSource扩展常见的实现方式有:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是RDBMS、文件,甚至是VCS等。这样做的方式是简单,缺点是无法及时获取变更。

  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。

Sentinel目前支持以下数据源扩展:

    1. Pull-based: 文件、Consul
    1. Push-based: ZooKeeper, Redis, Nacos, Apollo, etcd

Pull 模式

原理:

  • 扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等。
  • pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry 中。

过程如下:


Pull Demo

  • 1、添加配置

    com.alibaba.csp
    sentinel-datasource-extension

  • 2、编写持久化代码,实现com.alibaba.csp.sentinel.init.InitFunc
    • 代码参考自:传送门
import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
import com.alibaba.csp.sentinel.datasource.*;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * FileDataSourceInit for : 自定义Sentinel存储文件数据源加载类
 */
public class FileDataSourceInit implements InitFunc {
    @Override
    public void init() throws Exception {
        // TIPS: 如果你对这个路径不喜欢,可修改为你喜欢的路径
        String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";
        String hotParamFlowRulePath = ruleDir + "/param-flow-rule.json";

        this.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(hotParamFlowRulePath);
        // 流控规则
        ReadableDataSource> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        // 将可读数据源注册至FlowRuleManager
        // 这样当规则文件发生变化时,就会更新规则到内存
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中
        // 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降级规则
        ReadableDataSource> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource> degradeRuleWDS = new FileWritableDataSource<>(
                degradeRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);

        // 系统规则
        ReadableDataSource> systemRuleRDS = new FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource> systemRuleWDS = new FileWritableDataSource<>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

        // 授权规则
        ReadableDataSource> authorityRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource> authorityRuleWDS = new FileWritableDataSource<>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);

        // 热点参数规则
        ReadableDataSource> hotParamFlowRuleRDS = new FileRefreshableDataSource<>(
                hotParamFlowRulePath,
                hotParamFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(hotParamFlowRuleRDS.getProperty());
        WritableDataSource> paramFlowRuleWDS = new FileWritableDataSource<>(
                hotParamFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }

    /**
     * 流控规则对象转换
     */
    private Converter> flowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference>() {
            }
    );
    /**
     * 降级规则对象转换
     */
    private Converter> degradeRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference>() {
            }
    );
    /**
     * 系统规则对象转换
     */
    private Converter> systemRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference>() {
            }
    );

    /**
     * 授权规则对象转换
     */
    private Converter> authorityRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference>() {
            }
    );

    /**
     * 热点规则对象转换
     */
    private Converter> hotParamFlowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference>() {
            }
    );

    /**
     * 创建目录
     *
     * @param filePath
     */
    private void mkdirIfNotExits(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    /**
     * 创建文件
     *
     * @param filePath
     * @throws IOException
     */
    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }

    private  String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}
  • 3、启用上述代码
    resource 目录下创建 resources/META-INF/services 目录并创建文件com.alibaba.csp.sentinel.init.InitFunc ,内容为:
com.sxzhongf.sharedcenter.configuration.sentinel.datasource.FileDataSourceInit

Pull 优缺点

  • 优点

    • 1、简单,无任何依赖
    • 2、没有额外依赖
  • 缺点

  • 1、不保证一致性(规则是使用FileRefreshableDataSource定时更新,会有延迟)

  • 2、实时性不保证(规则是使用FileRefreshableDataSource定时更新)

  • 3、拉取过于频繁也可能会有性能问题

  • 4、由于文件存储于本地,容易丢失

  • 参考资料:

    • 1、ITMUCH
    • 2、Sentinel WIKI

Push 模式

生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper、Nacos、Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:

推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource接口端监听规则中心实时获取变更,流程如下:

实现原理
  • 控制台推送规则到Nacos/远程配置中心
  • Sentinel client 监听Nacos配置变化,更新本地缓存

Sentinel dashboard 加工

Dashboard 规则改造主要通过2个接口:

com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider
com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher
  • Download Sentinel Source Code
  • 修改原sentinel-dashboard项目下的POM文件


    com.alibaba.csp
    sentinel-datasource-nacos
    
    

  • 偷懒模式:复制sentinel-dashboard项目下test下的nacos包(
    src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacossrc/main/java/com/alibaba/csp/sentinel/dashboard/rule

修改NacosConfig中的指向Nacos 的ip和端口

@Configuration
public class NacosConfig {

    @Bean
    public Converter, String> flowRuleEntityEncoder() {
        return JSON::toJSONString;
    }

    @Bean
    public Converter> flowRuleEntityDecoder() {
        return s -> JSON.parseArray(s, FlowRuleEntity.class);
    }

    @Bean
    public ConfigService nacosConfigService() throws Exception {
        //设置Nacos的ip和端口
        return ConfigFactory.createConfigService("localhost:8848");
    }
}
  • 修改controller中的默认provider & publisher
    com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2中
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {

    @Autowired
    //@Qualifier("flowRuleDefaultProvider")
    @Qualifier("flowRuleNacosProvider")
    private DynamicRuleProvider> ruleProvider;
    
    @Autowired
    //@Qualifier("flowRuleDefaultPublisher")
    @Qualifier("flowRuleNacosPublisher")
    private DynamicRulePublisher> rulePublisher;
    
}
  • 打开 ·/Sentinel-1.6.2/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html·文件,修改代码:

    
        


------------改为

  •   流控规则 Nacos
  • Dashboard中要修改的代码已经好了。

    重新启动 Sentinel-dashboard mvn clean package -DskipTests

    相关微服务设置

    • 添加依赖
      
          com.alibaba.csp
          sentinel-datasource-nacos
      
    
    • 添加配置
    spring:
      cloud:
        sentinel:
          datasource:
            yibo_flow:
              nacos:
                server-addr: localhost:8848
                dataId: ${spring.application.name}-flow-rules
                groupId: SENTINEL_GROUP
                # 规则类型,取值见:org.springframework.cloud.alibaba.sentinel.datasource.RuleType
                rule_type: flow
            yibo_degrade:
              nacos:
                server-addr: localhost:8848
                dataId: ${spring.application.name}-degrade-rules
                groupId: SENTINEL_GROUP
                rule-type: degrade
    

    测试效果

    Sentinel 添加流控规则:


    Nacos 查看同步的配置:


    动态数据源支持

    SentinelProperties 内部提供了 TreeMap 类型的 datasource 属性用于配置数据源信息。

    spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json
    spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
    #spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
    #spring.cloud.sentinel.datasource.ds1.file.data-type=custom
    #spring.cloud.sentinel.datasource.ds1.file.converter-class=com.alibaba.cloud.examples.JsonFlowRuleListConverter
    #spring.cloud.sentinel.datasource.ds1.file.rule-type=flow
    
    spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848
    spring.cloud.sentinel.datasource.ds2.nacos.data-id=sentinel
    spring.cloud.sentinel.datasource.ds2.nacos.group-id=DEFAULT_GROUP
    spring.cloud.sentinel.datasource.ds2.nacos.data-type=json
    spring.cloud.sentinel.datasource.ds2.nacos.rule-type=degrade
    
    spring.cloud.sentinel.datasource.ds3.zk.path = /Sentinel-Demo/SYSTEM-CODE-DEMO-FLOW
    spring.cloud.sentinel.datasource.ds3.zk.server-addr = localhost:2181
    spring.cloud.sentinel.datasource.ds3.zk.rule-type=authority
    
    spring.cloud.sentinel.datasource.ds4.apollo.namespace-name = application
    spring.cloud.sentinel.datasource.ds4.apollo.flow-rules-key = sentinel
    spring.cloud.sentinel.datasource.ds4.apollo.default-flow-rule-value = test
    spring.cloud.sentinel.datasource.ds4.apollo.rule-type=param-flow
    

    配置说明:

    spring.cloud.sentinel.transport.dashboard:sentinel dashboard的访问地址,根据上面准备工作中启动的实例配置。
    spring.cloud.sentinel.datasource.ds.nacos.server-addr:nacos的访问地址,根据上面准备工作中启动的实例配置。
    spring.cloud.sentinel.datasource.ds.nacos.groupId:nacos中存储规则的groupId。
    spring.cloud.sentinel.datasource.ds.nacos.dataId:nacos中存储规则的dataId。
    spring.cloud.sentinel.datasource.ds.nacos.rule-type:该参数是spring cloud alibaba升级到0.2.2之后增加的配置,用来定义存储的规则类型。
    

    所有的规则类型可查看枚举类:org.springframework.cloud.alibaba.sentinel.datasource.RuleType,每种规则的定义格式可以通过各枚举值中定义的规则对象来查看,比如限流规则可查看:com.alibaba.csp.sentinel.slots.block.flow.FlowRule。这里对于dataId使用了${spring.application.name}变量,这样可以根据应用名来区分不同的规则配置。

    注意:

    由于版本迭代关系,Github Wiki中的文档信息不一定适用所有版本。比如:并没有spring.cloud.sentinel.datasource.ds2.nacos.rule-type这个参数。所以,读者在使用的时候,可以通过查看org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePropertiesConfiguration和org.springframework.cloud.alibaba.sentinel.datasource.config.NacosDataSourceProperties两个类来分析具体的配置内容,会更为准确。

    • Nacos中创建限流规则的配置,比如:springcloudalibaba-sentinel-nacos.json
    [
        {
            "resource":"/sentinelNacos/hello",
            "limitApp":"default",
            "grade":1,
            "count":3,
            "strategy":0,
            "controlBehavior":0,
            "clusterMode":false
        }
    ]
    

    可以看到上面配置规则是一个数组类型,数组中的每个对象是针对每一个保护资源的配置对象,每个对象中的属性解释如下:

    resource:资源名,即限流规则的作用对象
    • limitApp:流控针对的调用来源,若为 default 则不区分调用来源。
    • grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制。
    • count:限流阈值。
    • strategy:调用关系限流策略。
    • controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)。
    • clusterMode:是否为集群模式。

    参考:
    https://www.bookstack.cn/read/sentinel-v1.7/9eec10a866f4e671.md

    https://blog.csdn.net/m0_37661458/article/details/106605477

    https://www.pianshen.com/article/44641395129/

    https://www.cnblogs.com/zhangpan1244/p/11228020.html

    你可能感兴趣的:(Spring Cloud Alibaba——Sentinel规则持久化)