Sentinel 控制台集成 Nacos 实现规则配置双向同步和持久化存储(提供改造后源码)

server/2025/2/1 1:28:41/

目录

    • 一、前言
    • 二、Sentinel 控制台规则推送实现原理
    • 三、Sentinel控制台源码改造前置准备工作
      • 3.1、本文使用各组件版本
      • 3.2、下载Sentinel控制台源码
      • 3.3、启动Sentinel控制台
      • 3.4、应用服务实现 Sentinel 客户端动态获取 Nacos 规则配置
      • 3.4.2、Nacos配置中心添加规则json
        • 3.4.2.1、添加流控规则json
        • 3.4.2.2、添加熔断规则json(kerwin-user-degrade-rules)
      • 3.4.3、应用配置文件配置加载Nacos配置中心流控熔断规则json
      • 3.4.4、查看Sentinel控制台是否加载到Nacos中规则配置
    • 四、Sentinel控制台源码改造(这里只实现了流控和熔断持久化)
      • 4.1、第一步(调整POM引用包)
      • 4.2、第二步(添加集成Nacos配置中心操作实现)
      • 4.3、第三步(修改流控控制器FlowControllerV1)
      • 4.4、第四步(修改熔断控制器DegradeController)
    • 五、测试改造后效果
    • 六、总结
    • 七、源码地址

一、前言

      Sentinel 控制台默认情况下新增修改规则后是将规则推送至客户端并直接更新到内存中,客户端重启后规则就会丢失,但在实际应用中,为了实现配置的动态调整和持久化,通常会结合配置中心来使用。本文将详细介绍 Sentinel控制台结合 Nacos 配置中心,实现配置的双向同步与持久化存储。

二、Sentinel 控制台规则推送实现原理

      要想对Sentinel控制台源码进行改造需要先对整个规则推送逻辑进行了解,这里引用 官方手册 做说明。

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

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

原始模式

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

在这里插入图片描述

Pull模式

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

在这里插入图片描述

首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。

这种实现方法好处是简单,不引入新的依赖,坏处是无法保证监控数据的一致性。

Push模式

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

在这里插入图片描述

三、Sentinel控制台源码改造前置准备工作

      我们这里改造是通过Push模式实现,应用客户端集成sentinel-datasource-nacos实现动态拉取Nacos配置,Sentinel控制台通过Push模式将规则推送到Nacos配置中心,应用客户端监听Nacos规则配置实现动态刷新规则。

3.1、本文使用各组件版本

      部分组件对版本兼容要求其实没有那么高,比如Nacos,不一定要按照官方推荐版本,差几个小版本没有什么影响,我本地使用的一直是Nacos2.0.2,代码集成实现基本都一样。

JDK:1.8.0
Spring-Boot:2.3.12.RELEASE
Spring-Cloud:Hoxton.SR12
Spring-Cloud-Alibaba:2.2.9.RELEASE
Nacos:2.0.2
Sentinel:1.8.5

官方版本适配说明

3.2、下载Sentinel控制台源码

giehub下载地址

在这里插入图片描述

要是访问不了github也可以直接直接下载我改造好的源码,文章末尾会提供一个gitee下载地址。

3.3、启动Sentinel控制台

  • 修改配置文件
    在这里插入图片描述

  • 启动控制台
    我这里会将控制台本身也注册上去,这里添加一个启动参数-Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard,如果不想注册也可以不用加。
    在这里插入图片描述

3.4、应用服务实现 Sentinel 客户端动态获取 Nacos 规则配置

sentinelnacos_62">3.4.1、添加sentinel集成nacos包

      sentinel读取nacos配置需要添加这个适配包。

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

3.4.2、Nacos配置中心添加规则json

3.4.2.1、添加流控规则json
  • Nacos配置中心添加流控规则kerwin-user-flow-rules,Data ID自己定义即可,后面配置时记得修改就行
    在这里插入图片描述

  • 规则JSON(注意需要带上app字段,填写该应用的注册名称)

    [{"app": "kerwin-user","resource": "youResource","limitApp": "default","grade": 1,"count": 10,"strategy": 0,"controlBehavior": 0,"clusterMode": false}
    ]
    
3.4.2.2、添加熔断规则json(kerwin-user-degrade-rules)
  • Nacos配置中心添加流控规则kerwin-user-degrade-rules,Data ID自己定义即可,后面配置时记得修改就行
    在这里插入图片描述

  • 规则JSON(注意需要带上app字段,填写该应用的注册名称)

    [{"app": "kerwin-user","resource": "youResource","limitApp": "default","count": 0.5,"timeWindow": 10,"grade": 0,"minRequestAmount": 5,"statIntervalMs": 10000,"slowRatioThreshold": 0.5,"maxAllowedRt": 200}
    ]
    

3.4.3、应用配置文件配置加载Nacos配置中心流控熔断规则json

spring:cloud:sentinel:# 设置sentinel为热加载 默认为falseeager: true# 使用nacos来做持久化datasource:ds-flow:nacos:server-addr: 172.16.8.169:8848dataId: kerwin-user-flow-rulesusername: nacospassword: nacosnamespace: sentinel-rulesgroupId: DEFAULT_GROUP## 配置存储的格式data-type: json## rule-type设置对应得规则类型,总共七大类型,在com.alibaba.cloud.sentinel.datasource.RuleType这个枚举类中有体现## flow:流控  degrade:熔断降级  param-flow:热点参数规则  system:系统保护规则  authority:授权规则rule-type: flow## 配置熔断降级规则,名字任意ds-degrade:nacos:server-addr: 172.16.8.169:8848dataId: kerwin-user-degrade-rulesusername: nacospassword: nacosnamespace: sentinel-rulesgroupId: DEFAULT_GROUPdata-type: jsonrule-type: degrade

3.4.4、查看Sentinel控制台是否加载到Nacos中规则配置

  • 流控规则
    在这里插入图片描述
  • 熔断规则
    在这里插入图片描述

这里可以看到应用读取Nacos中的规则已经可以正常加载到Sentinel控制台,后面准备开始改造Sentinel控制台源码。

四、Sentinel控制台源码改造(这里只实现了流控和熔断持久化)

      sentinel-dashboard源码中提供了集成流控规则操作Nacosdemo,如果想看源码中demo可以查看test测试目录的com.alibaba.csp.sentinel.dashboard.rule.nacos包,这个包中存实现了流控规则集成Nacos操作实现,在main目录下的com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2类提供了调用示例,目前默认情况下前端页面还是调用的com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1控制器。

      sentinel-dashboard本文中就不详细说明如何通过源码提供的demo进行改造了,篇幅比较长,这里直接提供需要调整和适配的源码,直接贴上即可。

4.1、第一步(调整POM引用包)

去掉sentinel-dashboard模块中引入sentinel-datasource-nacos依赖的scop,默认配置只在测试时引用,去掉后Reload一下Maven
在这里插入图片描述

4.2、第二步(添加集成Nacos配置中心操作实现)

com.alibaba.csp.sentinel.dashboard.rule目录下创建一个nacos目录,并将下面代码都贴到nacos目录。

在这里插入图片描述

  • DegradeRuleNacosProvider:熔断规则Nacos配置规则拉取实现
java">import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;@Component("degradeRuleNacosProvider")
public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {@Autowiredprivate ConfigService configService;@Autowiredprivate Converter<String, List<DegradeRuleEntity>> converter;@Overridepublic 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);}
}
  • DegradeRuleNacosPublisher:熔断规则Nacos配置规则Push实现
java">import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.List;@Component("degradeRuleNacosPublisher")
public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {@Autowiredprivate ConfigService configService;@Autowiredprivate Converter<List<DegradeRuleEntity>, String> converter;@Overridepublic 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), ConfigType.JSON.getType());}
}
  • FlowRuleNacosProvider:流控规则Nacos配置规则拉取实现
java">import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {@Autowiredprivate ConfigService configService;@Autowiredprivate Converter<String, List<FlowRuleEntity>> converter;@Overridepublic List<FlowRuleEntity> getRules(String appName) throws Exception {String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,NacosConfigUtil.GROUP_ID, 3000);if (StringUtil.isEmpty(rules)) {return new ArrayList<>();}return converter.convert(rules);}
}
  • FlowRuleNacosPublisher:流控规则Nacos配置规则Push实现
java">import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.List;@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {@Autowiredprivate ConfigService configService;@Autowiredprivate Converter<List<FlowRuleEntity>, String> converter;@Overridepublic void publish(String app, List<FlowRuleEntity> rules) throws Exception {AssertUtil.notEmpty(app, "app name cannot be empty");if (rules == null) {return;}configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,NacosConfigUtil.GROUP_ID, converter.convert(rules), ConfigType.JSON.getType());}
}
  • NacosConfig:Nacos配置类
java">import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.List;
import java.util.Properties;@Configuration
public class NacosConfig {@Beanpublic Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {return JSON::toJSONString;}@Beanpublic Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() {return JSON::toJSONString;}@Beanpublic Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {return s -> JSON.parseArray(s, FlowRuleEntity.class);}@Beanpublic Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() {return s -> JSON.parseArray(s, DegradeRuleEntity.class);}@Beanpublic ConfigService nacosConfigService() throws Exception {// 配置注册中心 和 namespace// TODO 这里为了方便演示直接硬编码配置信息,需要线上使用自己读取配置文件Properties properties = new Properties();properties.put(PropertyKeyConst.SERVER_ADDR,"172.16.8.169:8848");properties.put(PropertyKeyConst.NAMESPACE,"sentinel-rules"); // 命名空间,要和客户端配置的一样properties.put(PropertyKeyConst.USERNAME,"nacos");properties.put(PropertyKeyConst.PASSWORD,"nacos");return ConfigFactory.createConfigService(properties);}
}
  • NacosConfigUtil:公共配置参数
java">public final class NacosConfigUtil {// Nacos分组ID 要和客户端一样public static final String GROUP_ID = "DEFAULT_GROUP";// 流控规则后缀 要和客户端一样public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";//熔断规则后缀 要和客户端一样public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-rules";public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules";public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map";/*** cc for `cluster-client`*/public static final String CLIENT_CONFIG_DATA_ID_POSTFIX = "-cc-config";/*** cs for `cluster-server`*/public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX = "-cs-transport-config";public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX = "-cs-flow-config";public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX = "-cs-namespace-set";private NacosConfigUtil() {}
}

4.3、第三步(修改流控控制器FlowControllerV1)

直接将com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1中代码全部替换即可。

java">import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);@Autowiredprivate InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;@Autowired
//    @Qualifier("flowRuleDefaultProvider")@Qualifier("flowRuleNacosProvider")private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;@Autowired
//    @Qualifier("flowRuleDefaultPublisher")@Qualifier("flowRuleNacosPublisher")private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;@GetMapping("/rules")@AuthAction(PrivilegeType.READ_RULE)public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app) {if (StringUtil.isEmpty(app)) {return Result.ofFail(-1, "app can't be null or empty");}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);}}private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {if (entity == null) {return Result.ofFail(-1, "invalid body");}if (StringUtil.isBlank(entity.getApp())) {return Result.ofFail(-1, "app can't be null or empty");}if (StringUtil.isBlank(entity.getLimitApp())) {return Result.ofFail(-1, "limitApp can't be null or empty");}if (StringUtil.isBlank(entity.getResource())) {return Result.ofFail(-1, "resource can't be null or empty");}if (entity.getGrade() == null) {return Result.ofFail(-1, "grade can't be null");}if (entity.getGrade() != 0 && entity.getGrade() != 1) {return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");}if (entity.getCount() == null || entity.getCount() < 0) {return Result.ofFail(-1, "count should be at lease zero");}if (entity.getStrategy() == null) {return Result.ofFail(-1, "strategy can't be null");}if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");}if (entity.getControlBehavior() == null) {return Result.ofFail(-1, "controlBehavior can't be null");}int controlBehavior = entity.getControlBehavior();if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");}if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");}if (entity.isClusterMode() && entity.getClusterConfig() == null) {return Result.ofFail(-1, "cluster config should be valid");}return null;}@PostMapping("/rule")@AuthAction(value = 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());} catch (Throwable throwable) {logger.error("Failed to add flow rule", throwable);return Result.ofThrowable(-1, throwable);}return Result.ofSuccess(entity);}@PutMapping("/save.json")@AuthAction(PrivilegeType.WRITE_RULE)public Result<FlowRuleEntity> apiUpdateFlowRule(Long id, String app,String limitApp, String resource, Integer grade,Double count, Integer strategy, String refResource,Integer controlBehavior, Integer warmUpPeriodSec,Integer maxQueueingTimeMs) {if (id == null) {return Result.ofFail(-1, "id can't be null");}FlowRuleEntity entity = repository.findById(id);if (entity == null) {return Result.ofFail(-1, "id " + id + " dose not exist");}if (StringUtil.isNotBlank(app)) {entity.setApp(app.trim());}if (StringUtil.isNotBlank(limitApp)) {entity.setLimitApp(limitApp.trim());}if (StringUtil.isNotBlank(resource)) {entity.setResource(resource.trim());}if (grade != null) {if (grade != 0 && grade != 1) {return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");}entity.setGrade(grade);}if (count != null) {entity.setCount(count);}if (strategy != null) {if (strategy != 0 && strategy != 1 && strategy != 2) {return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got");}entity.setStrategy(strategy);if (strategy != 0) {if (StringUtil.isBlank(refResource)) {return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");}entity.setRefResource(refResource.trim());}}if (controlBehavior != null) {if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) {return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got");}if (controlBehavior == 1 && warmUpPeriodSec == null) {return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");}if (controlBehavior == 2 && maxQueueingTimeMs == null) {return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");}entity.setControlBehavior(controlBehavior);if (warmUpPeriodSec != null) {entity.setWarmUpPeriodSec(warmUpPeriodSec);}if (maxQueueingTimeMs != null) {entity.setMaxQueueingTimeMs(maxQueueingTimeMs);}}Date date = new Date();entity.setGmtModified(date);try {entity = repository.save(entity);if (entity == null) {return Result.ofFail(-1, "save entity fail: null");}publishRules(entity.getApp());return Result.ofSuccess(entity);} catch (Throwable t) {Throwable e = t instanceof ExecutionException ? t.getCause() : t;logger.error("Error when updating flow rules, app={}, ip={}, ruleId={}", entity.getApp(),entity.getIp(), id, e);return Result.ofFail(-1, e.getMessage());}}@DeleteMapping("/delete.json")@AuthAction(PrivilegeType.WRITE_RULE)public Result<Long> apiDeleteFlowRule(Long id) {if (id == null) {return Result.ofFail(-1, "id can't be null");}FlowRuleEntity oldEntity = repository.findById(id);if (oldEntity == null) {return Result.ofSuccess(null);}try {repository.delete(id);} catch (Exception e) {return Result.ofFail(-1, e.getMessage());}try {publishRules(oldEntity.getApp());return Result.ofSuccess(id);} catch (Throwable t) {Throwable e = t instanceof ExecutionException ? t.getCause() : t;logger.error("Error when deleting flow rules, app={}, ip={}, id={}", oldEntity.getApp(),oldEntity.getIp(), id, e);return Result.ofFail(-1, e.getMessage());}}private void publishRules(/*@NonNull*/ String app) throws Exception {List<FlowRuleEntity> rules = repository.findAllByApp(app);rulePublisher.publish(app, rules);}
}

4.4、第四步(修改熔断控制器DegradeController)

直接将com.alibaba.csp.sentinel.dashboard.controller.DegradeController中代码全部替换即可。

java">import java.util.Date;
import java.util.List;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.util.StringUtil;import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.domain.Result;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/degrade")
public class DegradeController {private final Logger logger = LoggerFactory.getLogger(DegradeController.class);@Autowiredprivate RuleRepository<DegradeRuleEntity, Long> repository;@Autowiredprivate SentinelApiClient sentinelApiClient;@Autowiredprivate AppManagement appManagement;@Autowired@Qualifier("degradeRuleNacosProvider")private DynamicRuleProvider<List<DegradeRuleEntity>> ruleProvider;@Autowired@Qualifier("degradeRuleNacosPublisher")private DynamicRulePublisher<List<DegradeRuleEntity>> rulePublisher;@GetMapping("/rules.json")@AuthAction(PrivilegeType.READ_RULE)public Result<List<DegradeRuleEntity>> apiQueryMachineRules(String app, String ip, 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<DegradeRuleEntity> rules = sentinelApiClient.fetchDegradeRuleOfMachine(app, ip, port);List<DegradeRuleEntity> rules = ruleProvider.getRules(app);rules = repository.saveAll(rules);return Result.ofSuccess(rules);} catch (Throwable throwable) {logger.error("queryApps error:", throwable);return Result.ofThrowable(-1, throwable);}}@PostMapping("/rule")@AuthAction(PrivilegeType.WRITE_RULE)public Result<DegradeRuleEntity> apiAddRule(@RequestBody DegradeRuleEntity entity) {Result<DegradeRuleEntity> checkResult = checkEntityInternal(entity);if (checkResult != null) {return checkResult;}Date date = new Date();entity.setGmtCreate(date);entity.setGmtModified(date);try {entity = repository.save(entity);} catch (Throwable t) {logger.error("Failed to add new degrade rule, app={}, ip={}", entity.getApp(), entity.getIp(), t);return Result.ofThrowable(-1, t);}try {publishRules(entity.getApp());} catch (Throwable throwable) {logger.error("Failed to add degrade rule", throwable);return Result.ofThrowable(-1, throwable);}return Result.ofSuccess(entity);}@PutMapping("/rule/{id}")@AuthAction(PrivilegeType.WRITE_RULE)public Result<DegradeRuleEntity> apiUpdateRule(@PathVariable("id") Long id,@RequestBody DegradeRuleEntity entity) {if (id == null || id <= 0) {return Result.ofFail(-1, "id can't be null or negative");}DegradeRuleEntity oldEntity = repository.findById(id);if (oldEntity == null) {return Result.ofFail(-1, "Degrade rule does not exist, id=" + id);}entity.setApp(oldEntity.getApp());entity.setIp(oldEntity.getIp());entity.setPort(oldEntity.getPort());entity.setId(oldEntity.getId());Result<DegradeRuleEntity> checkResult = checkEntityInternal(entity);if (checkResult != null) {return checkResult;}entity.setGmtCreate(oldEntity.getGmtCreate());entity.setGmtModified(new Date());try {entity = repository.save(entity);} catch (Throwable t) {logger.error("Failed to save degrade rule, id={}, rule={}", id, entity, t);return Result.ofThrowable(-1, t);}try {publishRules(entity.getApp());} catch (Throwable throwable) {logger.error("Failed to update degrade rule", throwable);return Result.ofThrowable(-1, throwable);}return Result.ofSuccess(entity);}@DeleteMapping("/rule/{id}")@AuthAction(PrivilegeType.DELETE_RULE)public Result<Long> delete(@PathVariable("id") Long id) {if (id == null) {return Result.ofFail(-1, "id can't be null");}DegradeRuleEntity oldEntity = repository.findById(id);if (oldEntity == null) {return Result.ofSuccess(null);}try {repository.delete(id);} catch (Throwable throwable) {logger.error("Failed to delete degrade rule, id={}", id, throwable);return Result.ofThrowable(-1, throwable);}try {publishRules(oldEntity.getApp());} catch (Throwable throwable) {logger.error("Failed to add delete rule", throwable);return Result.ofThrowable(-1, throwable);}return Result.ofSuccess(id);}//    private boolean publishRules(String app, String ip, Integer port) {
//        List<DegradeRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
//        return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules);
//    }private void publishRules(/*@NonNull*/ String app) throws Exception {List<DegradeRuleEntity> rules = repository.findAllByApp(app);rulePublisher.publish(app, rules);}private <R> Result<R> checkEntityInternal(DegradeRuleEntity entity) {if (StringUtil.isBlank(entity.getApp())) {return Result.ofFail(-1, "app can't be blank");}
//        if (StringUtil.isBlank(entity.getIp())) {
//            return Result.ofFail(-1, "ip can't be null or empty");
//        }
//        if (entity.getPort() == null || entity.getPort() <= 0) {
//            return Result.ofFail(-1, "invalid port: " + entity.getPort());
//        }if (StringUtil.isBlank(entity.getLimitApp())) {entity.setLimitApp("default");
//            return Result.ofFail(-1, "limitApp can't be null or empty");}if (StringUtil.isBlank(entity.getResource())) {return Result.ofFail(-1, "resource can't be null or empty");}Double threshold = entity.getCount();if (threshold == null || threshold < 0) {return Result.ofFail(-1, "invalid threshold: " + threshold);}Integer recoveryTimeoutSec = entity.getTimeWindow();if (recoveryTimeoutSec == null || recoveryTimeoutSec <= 0) {return Result.ofFail(-1, "recoveryTimeout should be positive");}Integer strategy = entity.getGrade();if (strategy == null) {return Result.ofFail(-1, "circuit breaker strategy cannot be null");}if (strategy < CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()|| strategy > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {return Result.ofFail(-1, "Invalid circuit breaker strategy: " + strategy);}if (entity.getMinRequestAmount()  == null || entity.getMinRequestAmount() <= 0) {return Result.ofFail(-1, "Invalid minRequestAmount");}if (entity.getStatIntervalMs() == null || entity.getStatIntervalMs() <= 0) {return Result.ofFail(-1, "Invalid statInterval");}if (strategy == RuleConstant.DEGRADE_GRADE_RT) {Double slowRatio = entity.getSlowRatioThreshold();if (slowRatio == null) {return Result.ofFail(-1, "SlowRatioThreshold is required for slow request ratio strategy");} else if (slowRatio < 0 || slowRatio > 1) {return Result.ofFail(-1, "SlowRatioThreshold should be in range: [0.0, 1.0]");}} else if (strategy == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {if (threshold > 1) {return Result.ofFail(-1, "Ratio threshold should be in range: [0.0, 1.0]");}}return null;}
}

五、测试改造后效果

在这里插入图片描述

新增规则
在这里插入图片描述

新增或编辑规则后需要点一下刷新才能出来
在这里插入图片描述

查看Nacos规则,可以看到规则已经被写入到Nacos
在这里插入图片描述

六、总结

      本文只对流控和熔断规则做了实现,实现其它规则的持久化也比较简单,按照之前思路实现即可,以后有空再把其它常用规则也加上,还有网关应用的规则实现也差不多,只要找到对应的入口Controller把里面的调用逻辑替换操作Nacos配置中心就行。

七、源码地址

https://gitee.com/kerwin_code/kerwin-sentinel-1.8.5.git


http://www.ppmy.cn/server/163931.html

相关文章

使用iis服务器模拟本地资源服务器unityaddressables热更新出错记录

editor中设置了using exculexing 模拟远程加载addressable可以实现资源热更新&#xff0c;build后的软件却没有成功。 iis服务器中mime中需要设置bundle的文件扩展名&#xff0c;时editor成功&#xff0c;build后失败 原因没有设置hash的扩展名&#xff0c;设置后editor和buil…

volatile

文章目录 ResortSeqDemo.javaSingletonDemo.javaSingletonDemo2.javaVolatileDemo.java ResortSeqDemo.java package com.xd.thread;public class ResortSeqDemo {volatile int a0;boolean flagfalse;/*多线程下flagtrue可能先执行&#xff0c;还没走到a1就被挂起其他线程进入…

Redis内部数据结构--跳表详解

跳表 1. 什么是跳表--skiplist2. skiplist的效率如何保证3. 跳表的实现4. 跳表与平衡搜索树和哈希表的对比 1. 什么是跳表–skiplist skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树和哈希表的价值是一样的&#xff0c;可以作为…

我的毕设之路:(2)系统类型的论文写法

一般先进行毕设的设计与实现&#xff0c;再在现成毕设基础上进行描述形成文档&#xff0c;那么论文也就成形了。 1 需求分析&#xff1a;毕业设计根据开题报告和要求进行需求分析和功能确定&#xff0c;区分贴合主题的主要功能和拓展功能能&#xff0c;删除偏离无关紧要的功能…

一文讲解JVM中的G1垃圾收集器

接上一篇博文&#xff0c;这篇博文讲下JVM中的G1垃圾收集器 G1在JDK1.7时引入&#xff0c;在JDK9时取代了CMS成为默认的垃圾收集器&#xff1b; G1把Java堆划分为多个大小相等的独立区域Region&#xff0c;每个区域都可以扮演新生代&#xff08;Eden和Survivor&#xff09;或老…

mysql_store_result的概念和使用案例

mysql_store_result() 是 MySQL C API 中的一个函数&#xff0c;用于检索一个完整的结果集到一个客户端。当执行一个查询&#xff08;通常是 SELECT 查询&#xff09;并希望处理所有返回的数据时&#xff0c;可以使用此函数。 概念 mysql_store_result() 函数的原型如下&…

UE求职Demo开发日志#14 把特效换成通过GC应用,同时完善每段的特效,增加显示物品信息的UI

1 把之前的特效在GC里实现 在DefaultGame.ini里添加这个指定查找路径&#xff1a; [/Script/GameplayAbilities.AbilitySystemGlobals] GameplayCueNotifyPaths"/Game/GAS/GC" 添加标签&#xff1a; 在路径下创建一个GC蓝图&#xff0c;并设置好标签&#xff1a; 重…

01-时间与管理

时间与效率 一丶番茄时钟步骤好处 二丶86400s的财富利用时间的方法每天坚持写下一天计划 自我管理体系计划-行动-评价-回顾 一丶番茄时钟 一个计时器 一份任务清单,任务 步骤 每一个25分钟是一个番茄时钟 将工作时间划分为若干个25分钟的工作单元期间只专注于当前任务,遇到…