Sentinel之前已经搭建和应用成功了,但是它有一个很大的缺点就是官方没有提供持久化的方案,从项目源码上看感觉这款工具也没有完成的太好,所以需要我们去对它进行二次开发。要补充的功能大概如下:
1、将Sentinel接入nacos中,sentinel从nacos上进行参数读写。
2、使用sentinel的客户端开启nacos,可以读取nacos上关于sentinel的配置参数。
Sentinel接入Nacos实现读写功能。
1、下载sentinel的源码并且导入IDE,源码地址:GitHub - alibaba/Sentinel: A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
2、找到项目中sentinel-dashboard这个module,我们就修改这个module下面的代码。
3、修改pom文件,源文件中sentinel-datasource-nacos这个依赖只用来做test,现在要加入到主功能中,去掉<scope>test</scope>。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
4、修改application.yaml文件,添加nacos的配置信息,之后的代码需要用到。
nacos.server-addr=192.168.3.54:80
nacos.namespace=sentinel-rules
nacos.group=devops
nacos.username=nacos
nacos.password=nacos
5、在package路径com.alibaba.csp.sentinel.dashboard.rule下创建子路径nacos,将sentinel接入nacos的功能写入到这个路径下。官方在test里面其实写了限流的配置,所以我也是参考官方代码完成的其他几个功能,这也就解释了为什么pom里的依赖是test的原因。
6、先创建一个工具类,提前写一些nacos的配置项参数。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
public final class NacosConfigUtil {
// Nacos分组ID 要和客户端一样
// public static final String GROUP_ID = "devops";
// 流控规则后缀 要和客户端一样
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-flow-rules";
//系统规则
public static final String SYSTEM_DATA_ID_POSTFIX = "-system-rules";
//授权规则
public static final String AUTHORITY_DATA_ID_POSTFIX = "-authority-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() {}
}
7、创建一个config,实现与nacos的连接。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.*;
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Properties;
@Configuration
public class NacosConfig {
@Value("${nacos.server-addr:localhost:8848}")
private String serverAddr;
@Value("${nacos.namespace:sentinel-rules}")
private String namespace;
@Value("${nacos.group:devops}")
private String group;
@Value("${nacos.username:nacos}")
private String username;
@Value("${nacos.password:nacos}")
private String password;
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<List<ParamFlowRuleEntity>, String> paramFlowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<List<SystemRuleEntity>, String> systemRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<List<AuthorityRuleEntity>, String> authorityRuleEntityEncoder() {
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 Converter<String, List<ParamFlowRuleEntity>> paramFlowRuleEntityDecoder() {
return s -> JSON.parseArray(s, ParamFlowRuleEntity.class);
}
@Bean
public Converter<String, List<SystemRuleEntity>> systemRuleEntityDecoder() {
return s -> JSON.parseArray(s, SystemRuleEntity.class);
}
@Bean
public Converter<String, List<AuthorityRuleEntity>> authorityRuleEntityDecoder() {
return s -> JSON.parseArray(s, AuthorityRuleEntity.class);
}
@Bean
public ConfigService nacosConfigService() throws Exception {
// 配置注册中心 和 namespace
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, this.serverAddr);
properties.put(PropertyKeyConst.NAMESPACE, this.namespace); // 命名空间,要和客户端配置的一样
properties.put(PropertyKeyConst.USERNAME, this.username);
properties.put(PropertyKeyConst.PASSWORD, this.password);
return ConfigFactory.createConfigService(properties);
}
}
8、创建限流功能的一对功能文件:FlowRuleNacosProvider和FlowRuleNacosPublisher,FlowRuleNacosProvider就是让sentinel从nacos上获取配置,我们默认就用json格式,所以在上面的NacosConfig文件中有json字符串转对象的多个方法,FlowRuleNacosPublisher就是sentinel页面修改配置之后更新nacos。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<FlowRuleEntity>> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, group, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<FlowRuleEntity>, String> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public 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, group, converter.convert(rules), ConfigType.JSON.getType());
}
}
9、熔断也是两个文件,DegradeRuleNacosProvider和DegradeRuleNacosPublisher,就是仿照限流两个文件写的,里面改改参数即可,下面3个功能也一样。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component("degradeRuleNacosProvider")
public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<DegradeRuleEntity>> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public List<DegradeRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX, group, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("degradeRuleNacosPublisher")
public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<DegradeRuleEntity>, String> converter;
@Value("${nacos.group:devops}")
private String group;
@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, group, converter.convert(rules), ConfigType.JSON.getType());
}
}
10、热点规则
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component("paramFlowRuleNacosProvider")
public class ParamFlowRuleNacosProvider implements DynamicRuleProvider<List<ParamFlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<ParamFlowRuleEntity>> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public List<ParamFlowRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.PARAM_FLOW_DATA_ID_POSTFIX, group, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("paramFlowRuleNacosPublisher")
public class ParamFlowRuleNacosPublisher implements DynamicRulePublisher<List<ParamFlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<ParamFlowRuleEntity>, String> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public void publish(String app, List<ParamFlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + NacosConfigUtil.PARAM_FLOW_DATA_ID_POSTFIX, group, converter.convert(rules), ConfigType.JSON.getType());
}
}
11、系统规则
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component("systemRuleNacosProvider")
public class SystemRuleNacosProvider implements DynamicRuleProvider<List<SystemRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<SystemRuleEntity>> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public List<SystemRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.SYSTEM_DATA_ID_POSTFIX, group, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("systemRuleNacosPublisher")
public class SystemRuleNacosPublisher implements DynamicRulePublisher<List<SystemRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<SystemRuleEntity>, String> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public void publish(String app, List<SystemRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + NacosConfigUtil.SYSTEM_DATA_ID_POSTFIX, group, converter.convert(rules), ConfigType.JSON.getType());
}
}
12、授权规则
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component("authorityRuleNacosProvider")
public class AuthorityRuleNacosProvider implements DynamicRuleProvider<List<AuthorityRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<AuthorityRuleEntity>> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public List<AuthorityRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.AUTHORITY_DATA_ID_POSTFIX, group, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("authorityRuleNacosPublisher")
public class AuthorityRuleNacosPublisher implements DynamicRulePublisher<List<AuthorityRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<AuthorityRuleEntity>, String> converter;
@Value("${nacos.group:devops}")
private String group;
@Override
public void publish(String app, List<AuthorityRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + NacosConfigUtil.AUTHORITY_DATA_ID_POSTFIX, group, converter.convert(rules), ConfigType.JSON.getType());
}
}
13、还要修改Controller,因为web页面调用的时候要改成我们上面做的这些收发功能,限流这个Controller已经有了,但是有两个,我们用V1这个,有些教程用的V2,我建议用V1,因为官方还没有正式使用V2,而且这个工具更新不是太积极。
主要修改就是把com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1中的SentinelApiClient 替换成DynamicRuleProvider和DynamicRulePublisher,这里改动并不大,我直接贴出来更新后的文件做参考。
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.controller;
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);
@Autowired
private 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);
}
}
14、剩下4个功能都只有1个Controller,也是一样改法。
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.controller;
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);
@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");
}
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;
}
}
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.controller;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
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.dashboard.util.VersionUtils;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
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.Optional;
import java.util.concurrent.ExecutionException;
/**
* @author Eric Zhao
* @since 0.2.1
*/
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {
private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;
@Autowired
private RuleRepository<ParamFlowRuleEntity, Long> repository;
@Autowired
@Qualifier("paramFlowRuleNacosProvider")
private DynamicRuleProvider<List<ParamFlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("paramFlowRuleNacosPublisher")
private DynamicRulePublisher<List<ParamFlowRuleEntity>> rulePublisher;
private boolean checkIfSupported(String app, String ip, int port) {
try {
return Optional.ofNullable(appManagement.getDetailApp(app))
.flatMap(e -> e.getMachine(ip, port))
.flatMap(m -> VersionUtils.parseVersion(m.getVersion())
.map(v -> v.greaterOrEqual(version020)))
.orElse(true);
// If error occurred or cannot retrieve machine info, return true.
} catch (Exception ex) {
return true;
}
}
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<ParamFlowRuleEntity>> apiQueryAllRulesForMachine(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
if (!appManagement.isValidMachineOfApp(app, ip)) {
return Result.ofFail(-1, "given ip does not belong to given app");
}
if (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
// return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
// .thenApply(repository::saveAll)
// .thenApply(Result::ofSuccess)
// .get();
List<ParamFlowRuleEntity> rules = ruleProvider.getRules(app);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (ExecutionException ex) {
logger.error("Error when querying parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when querying parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private boolean isNotSupported(Throwable ex) {
return ex instanceof CommandNotFoundException;
}
@PostMapping("/rule")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<ParamFlowRuleEntity> apiAddParamFlowRule(@RequestBody ParamFlowRuleEntity entity) {
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(null);
entity.getRule().setResource(entity.getResource().trim());
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
// publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
publishRules(entity.getApp());
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when adding new parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when adding new parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private <R> Result<R> checkEntityInternal(ParamFlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "bad rule body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
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, "port can't be null");
}
if (entity.getRule() == null) {
return Result.ofFail(-1, "rule can't be null");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource name cannot be null or empty");
}
if (entity.getCount() < 0) {
return Result.ofFail(-1, "count should be valid");
}
if (entity.getGrade() != RuleConstant.FLOW_GRADE_QPS) {
return Result.ofFail(-1, "Unknown mode (blockGrade) for parameter flow control");
}
if (entity.getParamIdx() == null || entity.getParamIdx() < 0) {
return Result.ofFail(-1, "paramIdx should be valid");
}
if (entity.getDurationInSec() <= 0) {
return Result.ofFail(-1, "durationInSec should be valid");
}
if (entity.getControlBehavior() < 0) {
return Result.ofFail(-1, "controlBehavior should be valid");
}
return null;
}
@PutMapping("/rule/{id}")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<ParamFlowRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id,
@RequestBody ParamFlowRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);
// publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
publishRules(entity.getApp());
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when updating parameter flow rules, id=" + id, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null) {
return Result.ofFail(-1, "id cannot be null");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
// publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get();
publishRules(oldEntity.getApp());
return Result.ofSuccess(id);
} catch (ExecutionException ex) {
logger.error("Error when deleting parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when deleting parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
// private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
// List<ParamFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
// return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
// }
private void publishRules(/*@NonNull*/ String app) throws Exception {
List<ParamFlowRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
private <R> Result<R> unsupportedVersion() {
return Result.ofFail(4041,
"Sentinel client not supported for parameter flow control (unsupported version or dependency absent)");
}
private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2);
}
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.controller;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
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.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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
/**
* @author leyou(lihao)
*/
@RestController
@RequestMapping("/system")
public class SystemController {
private final Logger logger = LoggerFactory.getLogger(SystemController.class);
@Autowired
private RuleRepository<SystemRuleEntity, Long> repository;
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;
@Autowired
@Qualifier("systemRuleNacosProvider")
private DynamicRuleProvider<List<SystemRuleEntity>> ruleProvider;
@Autowired
@Qualifier("systemRuleNacosPublisher")
private DynamicRulePublisher<List<SystemRuleEntity>> rulePublisher;
private <R> Result<R> checkBasicParams(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");
}
if (port <= 0 || port > 65535) {
return Result.ofFail(-1, "port should be in (0, 65535)");
}
return null;
}
@GetMapping("/rules.json")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<SystemRuleEntity>> apiQueryMachineRules(String app, String ip,
Integer port) {
Result<List<SystemRuleEntity>> checkResult = checkBasicParams(app, ip, port);
if (checkResult != null) {
return checkResult;
}
try {
// List<SystemRuleEntity> rules = sentinelApiClient.fetchSystemRuleOfMachine(app, ip, port);
List<SystemRuleEntity> rules = ruleProvider.getRules(app);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Query machine system rules error", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private int countNotNullAndNotNegative(Number... values) {
int notNullCount = 0;
for (int i = 0; i < values.length; i++) {
if (values[i] != null && values[i].doubleValue() >= 0) {
notNullCount++;
}
}
return notNullCount;
}
@RequestMapping("/new.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<SystemRuleEntity> apiAdd(String app, String ip, Integer port,
Double highestSystemLoad, Double highestCpuUsage, Long avgRt,
Long maxThread, Double qps) {
Result<SystemRuleEntity> checkResult = checkBasicParams(app, ip, port);
if (checkResult != null) {
return checkResult;
}
int notNullCount = countNotNullAndNotNegative(highestSystemLoad, avgRt, maxThread, qps, highestCpuUsage);
if (notNullCount != 1) {
return Result.ofFail(-1, "only one of [highestSystemLoad, avgRt, maxThread, qps,highestCpuUsage] "
+ "value must be set > 0, but " + notNullCount + " values get");
}
if (null != highestCpuUsage && highestCpuUsage > 1) {
return Result.ofFail(-1, "highestCpuUsage must between [0.0, 1.0]");
}
SystemRuleEntity entity = new SystemRuleEntity();
entity.setApp(app.trim());
entity.setIp(ip.trim());
entity.setPort(port);
// -1 is a fake value
if (null != highestSystemLoad) {
entity.setHighestSystemLoad(highestSystemLoad);
} else {
entity.setHighestSystemLoad(-1D);
}
if (null != highestCpuUsage) {
entity.setHighestCpuUsage(highestCpuUsage);
} else {
entity.setHighestCpuUsage(-1D);
}
if (avgRt != null) {
entity.setAvgRt(avgRt);
} else {
entity.setAvgRt(-1L);
}
if (maxThread != null) {
entity.setMaxThread(maxThread);
} else {
entity.setMaxThread(-1L);
}
if (qps != null) {
entity.setQps(qps);
} else {
entity.setQps(-1D);
}
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("Add SystemRule error", throwable);
return Result.ofThrowable(-1, throwable);
}
// if (!publishRules(app, ip, port)) {
// logger.warn("Publish system rules fail after rule add");
// }
try {
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Publish system rules fail after rule add", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@GetMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<SystemRuleEntity> apiUpdateIfNotNull(Long id, String app, Double highestSystemLoad,
Double highestCpuUsage, Long avgRt, Long maxThread, Double qps) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
SystemRuleEntity 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 (highestSystemLoad != null) {
if (highestSystemLoad < 0) {
return Result.ofFail(-1, "highestSystemLoad must >= 0");
}
entity.setHighestSystemLoad(highestSystemLoad);
}
if (highestCpuUsage != null) {
if (highestCpuUsage < 0) {
return Result.ofFail(-1, "highestCpuUsage must >= 0");
}
if (highestCpuUsage > 1) {
return Result.ofFail(-1, "highestCpuUsage must <= 1");
}
entity.setHighestCpuUsage(highestCpuUsage);
}
if (avgRt != null) {
if (avgRt < 0) {
return Result.ofFail(-1, "avgRt must >= 0");
}
entity.setAvgRt(avgRt);
}
if (maxThread != null) {
if (maxThread < 0) {
return Result.ofFail(-1, "maxThread must >= 0");
}
entity.setMaxThread(maxThread);
}
if (qps != null) {
if (qps < 0) {
return Result.ofFail(-1, "qps must >= 0");
}
entity.setQps(qps);
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("save error:", throwable);
return Result.ofThrowable(-1, throwable);
}
// if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
// logger.info("publish system rules fail after rule update");
// }
try {
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("publish system rules fail after rule update", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@RequestMapping("/delete.json")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<?> delete(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
SystemRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Throwable throwable) {
logger.error("delete error:", throwable);
return Result.ofThrowable(-1, throwable);
}
// if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
// logger.info("publish system rules fail after rule delete");
// }
try {
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("publish system rules fail after rule delete", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(id);
}
// private boolean publishRules(String app, String ip, Integer port) {
// List<SystemRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
// return sentinelApiClient.setSystemRuleOfMachine(app, ip, port, rules);
// }
private void publishRules(/*@NonNull*/ String app) throws Exception {
List<SystemRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.controller;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
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.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;
/**
* @author Eric Zhao
* @since 0.2.1
*/
@RestController
@RequestMapping(value = "/authority")
public class AuthorityRuleController {
private final Logger logger = LoggerFactory.getLogger(AuthorityRuleController.class);
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private RuleRepository<AuthorityRuleEntity, Long> repository;
@Autowired
private AppManagement appManagement;
@Autowired
@Qualifier("authorityRuleNacosProvider")
private DynamicRuleProvider<List<AuthorityRuleEntity>> ruleProvider;
@Autowired
@Qualifier("authorityRuleNacosPublisher")
private DynamicRulePublisher<List<AuthorityRuleEntity>> rulePublisher;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<AuthorityRuleEntity>> apiQueryAllRulesForMachine(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
if (!appManagement.isValidMachineOfApp(app, ip)) {
return Result.ofFail(-1, "given ip does not belong to given app");
}
try {
// List<AuthorityRuleEntity> rules = sentinelApiClient.fetchAuthorityRulesOfMachine(app, ip, port);
List<AuthorityRuleEntity> rules = ruleProvider.getRules(app);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying authority rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private <R> Result<R> checkEntityInternal(AuthorityRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "bad rule body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
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, "port can't be null");
}
if (entity.getRule() == null) {
return Result.ofFail(-1, "rule can't be null");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource name cannot be null or empty");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp should be valid");
}
if (entity.getStrategy() != RuleConstant.AUTHORITY_WHITE
&& entity.getStrategy() != RuleConstant.AUTHORITY_BLACK) {
return Result.ofFail(-1, "Unknown strategy (must be blacklist or whitelist)");
}
return null;
}
@PostMapping("/rule")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<AuthorityRuleEntity> apiAddAuthorityRule(@RequestBody AuthorityRuleEntity entity) {
Result<AuthorityRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("Failed to add authority rule", throwable);
return Result.ofThrowable(-1, throwable);
}
// if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
// logger.info("Publish authority rules failed after rule add");
// }
try {
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Publish authority rules failed after rule add", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<AuthorityRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id,
@RequestBody AuthorityRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
Result<AuthorityRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(null);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "Failed to save authority rule");
}
} catch (Throwable throwable) {
logger.error("Failed to save authority rule", throwable);
return Result.ofThrowable(-1, throwable);
}
// if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
// logger.info("Publish authority rules failed after rule update");
// }
try {
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Publish authority rules failed after rule update", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null) {
return Result.ofFail(-1, "id cannot be null");
}
AuthorityRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
// if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
// logger.error("Publish authority rules failed after rule delete");
// }
try {
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("Publish authority rules failed after rule delete", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(id);
}
// private boolean publishRules(String app, String ip, Integer port) {
// List<AuthorityRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
// return sentinelApiClient.setAuthorityRuleOfMachine(app, ip, port, rules);
// }
private void publishRules(/*@NonNull*/ String app) throws Exception {
List<AuthorityRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
15、我们以nacos-client-demo为例,配置一下nacos,模拟一下sentinel对这个项目的控制,贴出来json格式做参考。新建一个namespace叫做sentinel-rules,这样sentinel的配置单独放,然后创建5个dataId来对应这5个功能。
nacos-client-demo-flow-rules:
[
{
"resource": "/nacos-client-demo/api/talk",
"limitApp": "default",
"grade": 1,
"count": 1,
"clusterMode": false,
"controlBehavior": 0,
"strategy": 0,
"warmUpPeriodSec": 10,
"maxQueueingTimeMs": 500,
"refResource": "rrr"
}
]
nacos-client-demo-degrade-rules:
[
{
"app": "nacos-client-demo",
"resource": "/nacos-client-demo/api/talk",
"limitApp": "default",
"count": 0.5,
"timeWindow": 10,
"grade": 0,
"minRequestAmount": 5,
"statIntervalMs": 10000,
"slowRatioThreshold": 0.5,
"maxAllowedRt": 200
}
]
nacos-client-demo-param-flow-rules:
[
{
"app": "nacos-client-demo",
"rule": {
"burstCount": 0,
"clusterConfig": {
"fallbackToLocalWhenFail": true,
"sampleCount": 10,
"thresholdType": 0,
"windowIntervalMs": 1000
},
"clusterMode": false,
"controlBehavior": 0,
"count": 3.0,
"durationInSec": 1,
"grade": 1,
"limitApp": "default",
"maxQueueingTimeMs": 0,
"paramFlowItemList": [
],
"paramIdx": 0,
"regex": false,
"resource": "/nacos-client-demo/api/talk"
}
}
]
nacos-client-demo-system-rules:
[
{
"app": "nacos-client-demo",
"avgRt": 1,
"highestCpuUsage": -1.0,
"highestSystemLoad": -1.0,
"maxThread": -1,
"qps": -1.0
}
]
nacos-client-demo-authority-rules:
[
{
"app": "nacos-client-demo",
"rule": {
"limitApp": "openfeign-demo",
"regex": false,
"resource": "/nacos-client-demo/api/talk",
"strategy": 1
}
}
]
16、这就完成了,这时候用IDE运行一下测试一下,如果启动后,sentinel里面已经存在nacos-client-demo的这些配置,证明修改完成。
微服务使用带有nacos持久化的Sentinel
Sentinel Dashboard完成了接入nacos持久化,但是这还不够,我们还要将nacos-client-demo这个项目也实现接入带有nacos持久化的sentinel,这样才能实现三方的闭环,SpringCloud对这个支持的很好,只要配置一下即可。
1、修改pom文件,引入sentinel支持nacos持久化的依赖。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2、application.yaml中添加sentinel对nacos持久化配置,就是配置nacos的地址以及上一节在nacos里添加的有关sentinel的几个dataId。
spring:
application:
name: nacos-client-demo
sentinel:
transport:
dashboard: 127.0.0.1:8080
clientIp: 192.168.3.164
http-method-specify: true # 请求前缀
# 设置sentinel为热加载 默认为false
eager: true
# 使用nacos来做持久化
datasource:
ds-flow:
nacos:
server-addr: 192.168.3.54:80
data-id: ${spring.application.name}-flow-rules
username: nacos
password: nacos
# namespace必须用id,所以创建namespace的时候尽量填入id
namespace: sentinel-rules
group-id: devops
## 配置存储的格式
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: 192.168.3.54:80
data-id: ${spring.application.name}-degrade-rules
username: nacos
password: nacos
# namespace必须用id,所以创建namespace的时候尽量填入id
namespace: sentinel-rules
group-id: devops
data-type: json
rule-type: degrade
3、重新运行项目即可,这时候的sentinel dashboard、nacos、nacos-client-demo三者两两之间数据互通了。