Sentinel 规则推送模式

Sentinel 规则的推送有三种模式:

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

原始模式

若不做任何修改,Dashboard 的推送规则方式是通过 api 将规则推送至客户端并直接更新到内存中:
微服务组件 Sentinel 持久化 - 图1
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境

拉模式

pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的;使用时需在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的WritableDataSourceRegistry
微服务组件 Sentinel 持久化 - 图2
首先 Sentinel 控制台通过 api 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中;使用 pull 模式的数据源时一般无需对 Sentinel 控制台进行改造;

官方 demo:sentinel-demo/sentinel-demo-dynamic-file-rule

引入依赖:

  1. <dependency>
  2. <groupId>com.alibaba.csp</groupId>
  3. <artifactId>sentinel-datasource-extension</artifactId>
  4. <version>1.8.0</version>
  5. </dependency>

核心代码:

  1. // FileRefreshableDataSource 会周期性的读取文件以获取规则,当文件有更新时会及时发现,并将规则更新到内存中。
  2. ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
  3. flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
  4. );
  5. // 将可读数据源注册至 FlowRuleManager.
  6. FlowRuleManager.register2Property(ds.getProperty());
  7. WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
  8. // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
  9. // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
  10. WritableDataSourceRegistry.registerFlowDataSource(wds);

拉模式改造

实现 InitFunc 接口,在 init 中处理 DataSource 初始化逻辑,并利用 SPI 机制实现加载
image.png
部分代码:

  1. public class FileDataSourceInit implements InitFunc {
  2. @Override
  3. public void init() throws Exception {
  4. //创建文件存储目录
  5. RuleFileUtils.mkdirIfNotExits(PersistenceRuleConstant.storePath);
  6. //创建规则文件
  7. RuleFileUtils.createFileIfNotExits(PersistenceRuleConstant.rulesMap);
  8. //处理流控规则逻辑
  9. dealFlowRules();
  10. // 处理降级规则
  11. dealDegradeRules();
  12. // 处理系统规则
  13. dealSystemRules();
  14. // 处理热点参数规则
  15. dealParamFlowRules();
  16. // 处理授权规则
  17. dealAuthRules();
  18. }
  19. private void dealFlowRules() throws FileNotFoundException {
  20. String ruleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.FLOW_RULE_PATH).toString();
  21. //创建流控规则的可读数据源
  22. ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource(
  23. ruleFilePath, RuleListConverterUtils.flowRuleListParser
  24. );
  25. // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
  26. FlowRuleManager.register2Property(flowRuleRDS.getProperty());
  27. WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<List<FlowRule>>(
  28. ruleFilePath, RuleListConverterUtils.flowFuleEnCoding
  29. );
  30. // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
  31. // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
  32. WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
  33. }
  34. private void dealDegradeRules() throws FileNotFoundException {
  35. //讲解规则文件路径
  36. String degradeRuleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.DEGRAGE_RULE_PATH).toString();
  37. //创建流控规则的可读数据源
  38. ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource(
  39. degradeRuleFilePath, RuleListConverterUtils.degradeRuleListParse
  40. );
  41. // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
  42. DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
  43. WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
  44. degradeRuleFilePath, RuleListConverterUtils.degradeRuleEnCoding
  45. );
  46. // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
  47. // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
  48. WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
  49. }
  50. private void dealSystemRules() throws FileNotFoundException {
  51. //讲解规则文件路径
  52. String systemRuleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.SYSTEM_RULE_PATH).toString();
  53. //创建流控规则的可读数据源
  54. ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource(
  55. systemRuleFilePath, RuleListConverterUtils.sysRuleListParse
  56. );
  57. // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
  58. SystemRuleManager.register2Property(systemRuleRDS.getProperty());
  59. WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
  60. systemRuleFilePath, RuleListConverterUtils.sysRuleEnCoding
  61. );
  62. // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
  63. // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
  64. WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
  65. }
  66. private void dealParamFlowRules() throws FileNotFoundException {
  67. //讲解规则文件路径
  68. String paramFlowRuleFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.HOT_PARAM_RULE).toString();
  69. //创建流控规则的可读数据源
  70. ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource(
  71. paramFlowRuleFilePath, RuleListConverterUtils.paramFlowRuleListParse
  72. );
  73. // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
  74. ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
  75. WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
  76. paramFlowRuleFilePath, RuleListConverterUtils.paramRuleEnCoding
  77. );
  78. // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
  79. // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
  80. ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
  81. }
  82. private void dealAuthRules() throws FileNotFoundException {
  83. //讲解规则文件路径
  84. String authFilePath = PersistenceRuleConstant.rulesMap.get(PersistenceRuleConstant.AUTH_RULE_PATH).toString();
  85. //创建流控规则的可读数据源
  86. ReadableDataSource<String, List<AuthorityRule>> authRuleRDS = new FileRefreshableDataSource(
  87. authFilePath, RuleListConverterUtils.authorityRuleParse
  88. );
  89. // 将可读数据源注册至FlowRuleManager 这样当规则文件发生变化时,就会更新规则到内存
  90. AuthorityRuleManager.register2Property(authRuleRDS.getProperty());
  91. WritableDataSource<List<AuthorityRule>> authRuleWDS = new FileWritableDataSource<>(
  92. authFilePath, RuleListConverterUtils.authorityEncoding
  93. );
  94. // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
  95. // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
  96. WritableDataSourceRegistry.registerAuthorityDataSource(authRuleWDS);
  97. }
  98. }

推模式

对于 push 模式的数据源,es:远程配置中心(zk、Nacos 等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地;因此推送规则正确的做法应该是配置中心控制台/Sentinel 控制台 --> 配置中心 --> Sentinel 数据源 --> Sentinel
微服务组件 Sentinel 持久化 - 图4

基于 Nacos 配置中心实现推送

官方 demo:sentinel-demo-nacos-datasource

引入依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.0</version>
</dependency>

核心代码:

// nacos server ip
private static final String remoteAddress = "localhost:8848";
// nacos group
private static final String groupId = "Sentinel:Demo";
// nacos dataId
private static final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule";
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

nacos 配置中心中配置流控规则:

[
  {
    "resource": "TestResource",
    "controlBehavior": 0,
    "count": 10.0,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0
  }
]

image.png
微服务中通过 yml 配置实现

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

引入依赖:

 <!--sentinel持久化 采用 Nacos 作为规则配置数据源-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

yml 配置:

spring:
  application:
    name: mall-user-sentinel-demo
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    sentinel:
      transport:
        # 添加sentinel的控制台地址
        dashboard: 127.0.0.1:8080
        # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

nacos 配置中心添加:

[
    {
        "resource": "userinfo",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

image.png

缺点:直接在 Sentinel Dashboard 中修改规则配置,配置中心的配置不会发生变化

基于 Sentinel 控制台实现推送

从 Sentinel 1.4.0 开始,Sentinel 控制台提供DynamicRulePublisherDynamicRuleProvider接口用于实现应用维度的规则推送和拉取

Sentinel Dashboard 改造

在 com.alibaba.csp.sentinel.dashboard.rule 包下创建 nacos 包,然后把各种场景的配置规则拉取和推送的实现类写到此包下:
image.pngimage.png
image.png

注:微服务接入 Sentinel client,yml 配置需匹配对应的规则后缀

image.png
进入 com.alibaba.csp.sentinel.dashboard.controller 包下修改对应的规则 controller 实现类
image.png
以流控规则为例,从 Nacos 配置中心获取所有的流控规则

@GetMapping("/rules")
    @AuthAction(PrivilegeType.READ_RULE)
    public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,
                                                             @RequestParam String ip,
                                                             @RequestParam Integer port) {

        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        try {
           // List<FlowRuleEntity> rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port);
            //从配置中心获取规则配置
            List<FlowRuleEntity> rules = ruleProvider.getRules(app, ip, port);
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (Throwable throwable) {
            logger.error("Error when querying flow rules", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

新增流控规则,会推送到 nacos 配置中心:

@PostMapping("/rule")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
        Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        entity.setId(null);
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        entity.setLimitApp(entity.getLimitApp().trim());
        entity.setResource(entity.getResource().trim());
        try {
            entity = repository.save(entity);

            //publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
            //发布规则到配置中心
            publishRules(entity.getApp());
            return Result.ofSuccess(entity);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Failed to add new flow rule, app={}, ip={}", entity.getApp(), entity.getIp(), e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

/**
     * 发布到配置中心
     * @param app
     * @throws Exception
     */
    private void publishRules(/*@NonNull*/ String app) throws Exception {
        List<FlowRuleEntity> rules = repository.findAllByApp(app);
        rulePublisher.publish(app, rules);
    }

测试:微服务接入改造后的 Sentinel Dashboard

引入依赖:

 <!--sentinel持久化 采用 Nacos 作为规则配置数据源-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

增加 yml 配置:

server:
  port: 8806

spring:
  application:
    name: mall-user-sentinel-rule-push-demo  #微服务名称

  #配置nacos注册中心地址
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    sentinel:
      transport:
        # 添加sentinel的控制台地址
        dashboard: 127.0.0.1:8080
        # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
        #port: 8719
      datasource:
#        ds1:   #名称自定义,唯一
#          nacos:
#            server-addr: 127.0.0.1:8848
#            dataId: ${spring.application.name}
#            groupId: DEFAULT_GROUP
#            data-type: json
#            rule-type: flow
        flow-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP   # 注意groupId对应Sentinel Dashboard中的定义
            data-type: json
            rule-type: flow
        degrade-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: degrade
        param-flow-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-param-flow-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: param-flow
        authority-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-authority-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: authority
        system-rules:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: system

在 Sentinel Dashboard 配置了流控规则,会在nacos 配置中心生成对应的配置

热点参数规则失效和解决思路

注:控制台改造后有可能出现规则不生效的情况,es:热点参数规则因为 Converter 解析 json 错误的原因会导致不生效
原因:改造 Dashboard,提交到 nacos 配置中心的数据是 ParamFlowRuleEntity 类型,微服务拉取配置要解析的是 ParamFlowRule 类型,会导致规则解析丢失数据,造成热点规则不生效;其他的规则原理也一样,存在失效的风险

提供两种解决思路:

  1. 自定义一个解析热点规则配置的解析器 FlowParamJsonConverter,继承 JsonConverter,重写 convert 方法;然后利用后置处理器替换 beanName 为”param-flow-rules-sentinel-nacos-datasource”的 converter 属性,注入 FlowParamJsonConverter ```java @Configuration public class ConverterConfig {

    @Bean(“sentinel-json-param-flow-converter2”) @Primary public JsonConverter jsonParamFlowConverter() {

     return new FlowParamJsonConverter(new ObjectMapper(), ParamFlowRule.class);
    

    } }

@Component public class FlowParamConverterBeanPostProcessor implements BeanPostProcessor {

@Autowired
private JsonConverter jsonParamFlowConverter;

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (beanName.equals("param-flow-rules-sentinel-nacos-datasource")) {
        NacosDataSourceFactoryBean nacosDataSourceFactoryBean = (NacosDataSourceFactoryBean) bean;
        nacosDataSourceFactoryBean.setConverter(jsonParamFlowConverter);
        return bean;
    }
    return bean;
}

}

public class FlowParamJsonConverter extends JsonConverter { Class ruleClass;

public FlowParamJsonConverter(ObjectMapper objectMapper, Class ruleClass) {
    super(objectMapper, ruleClass);
    this.ruleClass = ruleClass;
}

@Override
public Collection<Object> convert(String source) {
    List<Object> list = new ArrayList<>();
    JSONArray jsonArray = JSON.parseArray(source);
    for (int i = 0; i < jsonArray.size(); i++) {
        //解析rule属性
        JSONObject jsonObject = (JSONObject) jsonArray.getJSONObject(i).get("rule");
        Object object = JSON.toJavaObject(jsonObject, ruleClass);
        list.add(object);
    }
    return list;
}

} ```

  1. 改造 Sentinel Dashboard 控制台,发布配置时将 ParamFlowRuleEntity 转成 ParamFlowRule 类型,再发布到 Nacos 配置中心;从配置中心拉取配置后将 ParamFlowRule 转成 ParamFlowRuleEntity

image.png
image.png

Sentinel 规则持久化源码解析

https://www.processon.com/view/link/607fef267d9c08283ddc2f8d