nacos和sentinel限流和熔断的双向规则数据同步,规则数据持久化

限流和熔断的双向数据同步,废话不多说,直接展示

1、首先保证你已经安装了nacos,因为我们需要改sentinel代码,所以需要把sentinel源码拉下来,本地运行调试。

2、启动nacos,然后创建一个微服务,然后修改application.yaml文件

3、添加依赖
<dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
4、配置文件中配置相关信息 application.yaml 内容如下:
system:
  config:
    nacos:
      server: 127.0.0.1:8848  #nacos ip port
      user: nacos   #nacos 账号
      pwd: nacos   #nacos 密码
      namespace: 4a965b55-54a6-4c17-a6ef-b7bd239e7  #nacos namespace的id   如果有命名空间的话

server:
  port: 8081

spring:
  application:
    name: test-sentinel

  cloud:
    nacos:
      config:
        server-addr: ${system.config.nacos.server}
        application: ${spring.application.name}
        namespace: ${system.config.nacos.namespace}
        username: ${system.config.nacos.user}
        password: ${system.config.nacos.pwd}
        auto-refresh: true
        file-extension: yaml
        remote-first: true
        group: local
      discovery:
        application: ${spring.application.name}
        server-addr: ${system.config.nacos.server}
        auto-register: true
        namespace: ${system.config.nacos.namespace}
        username: ${system.config.nacos.user}
        password: ${system.config.nacos.pwd}
        group: local
    sentinel:
      transport:
        #控制台地址
        dashboard: localhost:8080
      # 启动直接加载配置
      eager: true

      # 使用nacos来做持久化
      datasource:
        ## 配置流控规则,名字任意
        ds-flow:
          nacos:
            ## nacos的地址
            server-addr: ${system.config.nacos.server}
            ## 配置ID
            dataId: ${spring.application.name}-flow
            ## 配置分组,默认是DEFAULT_GROUP
            groupId: test_sentinel
            ## 配置存储的格式
            data-type: json
            ## rule-type设置对应得规则类型,总共七大类型,在com.alibaba.cloud.sentinel.datasource.RuleType这个枚举类中有体现
            rule-type: flow
            username: ${system.config.nacos.user}
            password: ${system.config.nacos.pwd}
            namespace: ${system.config.nacos.namespace}
        ## 配置降级规则,名字任意
        ds-degrade:
          nacos:
            ## nacos的地址
            server-addr: ${system.config.nacos.server}
            ## 配置ID
            dataId: ${spring.application.name}-degrade
            ## 配置分组,默认是DEFAULT_GROUP
            groupId: test_sentinel
            ## 配置存储的格式
            data-type: json
            ## rule-type设置对应得规则类型,总共七大类型,在com.alibaba.cloud.sentinel.datasource.RuleType这个枚举类中有体现
            rule-type: degrade
            username: ${system.config.nacos.user}
            password: ${system.config.nacos.pwd}
            namespace: ${system.config.nacos.namespace}

5、在Nacos添加对应的规则配置,第一个是限流的,第二个是熔断的。

在这里插入图片描述

在这里插入图片描述

[
    {
        "app": "test-sentinel",
        "clusterMode": false,
        "controlBehavior": 0,
        "count": 2,
        "gmtModified": 1721721080442,
        "grade": 1,
        "id": 13,
        "limitApp": "default",
        "maxQueueingTimeMs": 500,
        "refResource": "rrr",
        "resource": "/test",
        "strategy": 0,
        "warmUpPeriodSec": 10
    }
]

在这里插入图片描述

[
    {
        "app": "test-sentinel",
        "count": 200,
        "gmtModified": 1721721169983,
        "grade": 0,
        "id": 4,
        "ip": "192.168.72.1",
        "limitApp": "default",
        "minRequestAmount": 10,
        "port": 8719,
        "resource": "/test",
        "slowRatioThreshold": 0.2,
        "statIntervalMs": 1000,
        "timeWindow": 10
    }
]
6、push模式,只要nacos中点击发布配置,相关规则配置就会推送到sentinel中。下面是sentinel的效果

在这里插入图片描述
在这里插入图片描述

7、JSON中到底怎么写?
7.1 流控规则
[
  {
    // 资源名
    "resource": "/test",
    // 针对来源,若为 default 则不区分调用来源
    "limitApp": "default",
    // 限流阈值类型(1:QPS;0:并发线程数)
    "grade": 1,
    // 阈值
    "count": 1,
    // 是否是集群模式
    "clusterMode": false,
    // 流控效果(0:快速失败;1:Warm Up(预热模式);2:排队等待)
    "controlBehavior": 0,
    // 流控模式(0:直接;1:关联;2:链路)
    "strategy": 0,
    // 预热时间(秒,预热模式需要此参数)
    "warmUpPeriodSec": 10,
    // 超时时间(排队等待模式需要此参数)
    "maxQueueingTimeMs": 500,
    // 关联资源、入口资源(关联、链路模式)
    "refResource": "rrr"
  }
]
7.2 降级规则
[
  {
   // 资源名
    "resource": "/test1",
    "limitApp": "default",
    // 熔断策略(0:慢调用比例,1:异常比率,2:异常计数)
    "grade": 0,
    // 最大RT、比例阈值、异常数
    "count": 200,
    // 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
    "slowRatioThreshold": 0.2,
    // 最小请求数
    "minRequestAmount": 5,
    // 当单位统计时长(类中默认1000)
    "statIntervalMs": 1000,
    // 熔断时长
    "timeWindow": 10
  }
]
7.3、热点规则
[
  {
   // 资源名
    "resource": "/test1",
    // 限流模式(QPS 模式,不可更改)
    "grade": 1,
    // 参数索引
    "paramIdx": 0,
    // 单机阈值
    "count": 13,
    // 统计窗口时长
    "durationInSec": 6,
    // 是否集群 默认false
    "clusterMode": 默认false,
    // 
    "burstCount": 0,
    // 集群模式配置
    "clusterConfig": {
      // 
      "fallbackToLocalWhenFail": true,
      // 
      "flowId": 2,
      // 
      "sampleCount": 10,
      // 
      "thresholdType": 0,
      // 
      "windowIntervalMs": 1000
    },
    // 流控效果(支持快速失败和匀速排队模式)
    "controlBehavior": 0,
    // 
    "limitApp": "default",
    // 
    "maxQueueingTimeMs": 0,
    // 高级选项
    "paramFlowItemList": [
      {
       // 参数类型
        "classType": "int",
       // 限流阈值
        "count": 222,
       // 参数值
        "object": "2"
      }
    ]
  }
]
7.4、系统规则
[
  {
   // RT
    "avgRt": 1,
    // CPU 使用率
    "highestCpuUsage": -1,
    // LOAD
    "highestSystemLoad": -1,
    // 线程数
    "maxThread": -1,
    // 入口 QPS
    "qps": -1
  }
]
7.5、授权规则
[
  {
    // 资源名
    "resource": "sentinel_spring_web_context",
   // 流控应用
    "limitApp": "/test",
    // 授权类型(0代表白名单;1代表黑名单。)
    "strategy": 0
  }
]

8、从sentinel推送规则到nacos中持久化。首先我先下载sentinel源码

下载地址:

https://github.com/alibaba/Sentinel/tags

8.1、找到sentinel-dashboard 模块test下面的 rule包下面的nacos包,把整个nacos包复制到main包下面的rule包下,至于我为什么多了两个文件,下面会解释,先不管。

在这里插入图片描述
在这里插入图片描述

8.2、去掉sentinel-datasource-nacos依赖的scop
<!-- for Nacos rule publisher sample -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <!-- 注释test -->
	<!--<scope>test</scope>-->
</dependency>
8.3、将FlowControllerV2中的代码复制到FlowControllerV1中

com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2这个是sentinel提供的demo,只需要将其中的代码全部覆盖到com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1中

8.4、修改FlowControllerV1中的代码

• 修改RequestMapping中的请求url为/v1/flow
• 修改ruleProvider、rulePublisher的依赖,修改后的代码如下:

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

这时候你会发现代码报红,缺少nacos api 的包,需要在maven引用。

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-api</artifactId>
    <version>1.4.2</version>
    <scope>compile</scope>
</dependency>
8.5、修改NacosConfigUtil类,修改GROUP_ID和FLOW_DATA_ID_POSTFIX,要和客户端一样。

这是上面配置的客户端信息:

## 配置ID
dataId: ${spring.application.name}-flow
## 配置分组,默认是DEFAULT_GROUP
groupId: test_sentinel

在这里插入图片描述

8.6、如果你不是本机nacos,有各种配置(如namespace,账号,密码等),需要修改NacosConfig类
@Bean
    public ConfigService nacosConfigService() throws Exception {
        //配置注册中心 和 namespace
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR,"nacos地址  ip:port");
        properties.put(PropertyKeyConst.NAMESPACE,"命名空间,要和和客户端配置的一样");
        properties.put(PropertyKeyConst.USERNAME,"nacos账号");
        properties.put(PropertyKeyConst.PASSWORD,"nacos密码");
        return ConfigFactory.createConfigService(properties);
    }
8.7、修改完成,然后运行sentinel源码,或者打包运行都行。然后修改sentinel控制台的流控规则,就会同步到nacos配置里面了。
9、上面是流控规则的修改,下面是熔断的修改。
9.1、增加两个类,直接把FlowRuleNacosProvider和FlowRuleNacosPublisher复制,改名为DegradeRuleNacosProvider和DegradeRuleNacosPublisher

在这里插入图片描述

9.2、修改对应的泛型为熔断实体对象,修改对应常量。 DegradeRuleNacosProvider 和 DegradeRuleNacosPublisher内容如下:

@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);
    }
}

@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));
    }
}
9.3、NacosConfig类 增加两个配置。下面直接贴出NacosConfig类完整代码。
@Configuration
public class NacosConfig {

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

    @Bean
    public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() {
        return JSON::toJSONString;
    }

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

    @Bean
    public Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() {
        return s -> JSON.parseArray(s, DegradeRuleEntity.class);
    }

    @Bean
    public ConfigService nacosConfigService() throws Exception {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR,"nacos地址  ip:port");
        properties.put(PropertyKeyConst.NAMESPACE,"命名空间,要和和客户端配置的一样");
        properties.put(PropertyKeyConst.USERNAME,"nacos账号");
        properties.put(PropertyKeyConst.PASSWORD,"nacos密码");
        return ConfigFactory.createConfigService(properties);
    }

}
9.4、NacosConfigUtil类增加熔断后缀的常量。
    //熔断后缀 要和客户端一样
    public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade";
9.5、修改DegradeController类,直接展示全部内容,复制粘贴就行
@RestController
@RequestMapping("/degrade")
public class DegradeController {

    private final Logger logger = LoggerFactory.getLogger(DegradeController.class);

    @Autowired
    private RuleRepository<DegradeRuleEntity, Long> repository;
    @Autowired
    private SentinelApiClient sentinelApiClient;
    @Autowired
    private 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");
        }
        if (!appManagement.isValidMachineOfApp(app, ip)) {
            return Result.ofFail(-1, "given ip does not belong to given app");
        }
        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 (!appManagement.isValidMachineOfApp(entity.getApp(), entity.getIp())) {
            return Result.ofFail(-1, "given ip does not belong to given app");
        }
        if (entity.getPort() == null || entity.getPort() <= 0) {
            return Result.ofFail(-1, "invalid port: " + entity.getPort());
        }
        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");
        }
        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;
    }
}
9.6、然后重新启动,尝试更改更改熔断规则,已经成功同步到nacos了,包括新增,更改,删除。其他的也规则可以参照这个来弄。
10、遇到的问题

遇到的问题: 我自己的情况会发现新增流控规则没问题,但是更新和删除会失败,修改有问题报404,新增是这个接口/v1/flow/rule ,修改是这个接口/v1/flow/save.json,然后你会发现FlowControllerV1接口了并没有/v1/flow/save.json接口。流控发现这个问题,但是熔断并没有。

如何处理:
1、第一可以修改前端,改掉接口地址,和请求参数。
2、第二修改后端,可以把之前V1的接口和现在V1接口结合,下面我直接贴出代码,复制粘贴就行。


@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {

    private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);

    @Autowired
    private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

    @Autowired
    @Qualifier("flowRuleNacosProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @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 = AuthService.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);
            //todo 更新为 publish 方式
            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");
            }

            // todo 更新为 publish 方式
            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 {
            //todo 更新为 publish 方式
            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);
    }
}

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值