一:sentinel持久化到nacos(全部配置)
二:sentinel控制台的改造(全部代码)
实现原理:
a) 控制台推送规则到Nacos/远程配置中心
b) Sentinel client 监听Nacos配置变化,更新本地缓存
配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel
```java
application-xxx.properties中增加配置
#jvm启动设置
#-Dproject.name=gh-demo-provide
#-Dcsp.sentinel.api.port=9000
#-Dcsp.sentinel.dashboard.server=127.0.0.1:8085
spring.application.name=gh-demo-provide
#同一服务器项目需要端用端口区分
spring.cloud.sentinel.transport.port=9000
#sentinel控制台地址
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8085
# sentinel dashboard
#流量控制
# nacos的访问地址,我这里是nigix代理nacos的集群地址
spring.cloud.sentinel.datasource.flow.nacos.server-addr=10.151.0.84:8848
#与sentinel控制台的namespeace统一
spring.cloud.sentinel.datasource.flow.nacos.namespace=16b7fa67-d3e0-47e4-bc0d-76056712afc4
#nacos中存储规则的dataId,对于dataId使用了${spring.application.name}变量,这样可以根据应用名来区分不同的规则配置
spring.cloud.sentinel.datasource.flow.nacos.data-id=${spring.application.name}-flow-rules
#nacos中存储规则的groupId
spring.cloud.sentinel.datasource.flow.nacos.group-id=SENTINEL_GROUP
#定义存储的规则类型
# 规则类型,取值见:
# org.springframework.cloud.alibaba.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.flow.nacos.rule-type=flow
#除流控以外的其他规则配置,可以选择性配置,也就是持久化哪个规则就配哪个
#熔断降级
spring.cloud.sentinel.datasource.degrade.nacos.server-addr=10.151.0.84:8848
spring.cloud.sentinel.datasource.degrade.nacos.namespace=16b7fa67-d3e0-47e4-bc0d-76056712afc4
spring.cloud.sentinel.datasource.degrade.nacos.data-id=${spring.application.name}-degrade-rules
spring.cloud.sentinel.datasource.degrade.nacos.group-id=SENTINEL_GROUP
spring.cloud.sentinel.datasource.degrade.nacos.rule-type=degrade
#系统规则
spring.cloud.sentinel.datasource.system.nacos.server-addr=10.151.0.84:8848
spring.cloud.sentinel.datasource.system.nacos.namespace=16b7fa67-d3e0-47e4-bc0d-76056712afc4
spring.cloud.sentinel.datasource.system.nacos.data-id=${spring.application.name}-system-rules
spring.cloud.sentinel.datasource.system.nacos.group-id=SENTINEL_GROUP
spring.cloud.sentinel.datasource.system.nacos.rule-type=system
#授权规则
spring.cloud.sentinel.datasource.authority.nacos.server-addr=10.151.0.84:8848
spring.cloud.sentinel.datasource.authority.nacos.namespace=16b7fa67-d3e0-47e4-bc0d-76056712afc4
spring.cloud.sentinel.datasource.authority.nacos.data-id=${spring.application.name}-authority-rules
spring.cloud.sentinel.datasource.authority.nacos.group-id=SENTINEL_GROUP
spring.cloud.sentinel.datasource.authority.nacos.rule-type=authority
#热点规则
spring.cloud.sentinel.datasource.param-flow.nacos.server-addr=10.151.0.84:8848
spring.cloud.sentinel.datasource.param-flow.nacos.namespace=16b7fa67-d3e0-47e4-bc0d-76056712afc4
spring.cloud.sentinel.datasource.param-flow.nacos.data-id=${spring.application.name}-param-flow-rules
spring.cloud.sentinel.datasource.param-flow.nacos.group-id=SENTINEL_GROUP
spring.cloud.sentinel.datasource.param-flow.nacos.rule-type=param-flow
由于sentinel都将规则内容存入内存中,在重启服务或者5分钟之后,规则将被清除,所有这里修改sentinel控制台,使规则在nacos中实现增删改的储存,实现持久化。
sentinel控制台application.properties中加入:
application.properties中增加配置
server.port=8085
#nacos的集群地址
nacos.address=10.151.0.84:8848
#二选一public为空,namespeace为uuid,这里与dubbo服务一致
#nacos.namespace=
nacos.namespace=16b7fa67-d3e0-47e4-bc0d-76056712afc4
public class JSONUtils {
public static String toJSONString(Object object) {
try {
return new ObjectMapper().writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public static JavaType getCollectionType(Class> collectionClass, Class>... elementClasses) {
return new ObjectMapper()
.getTypeFactory()
.constructParametricType(collectionClass, elementClasses);
}
public static List parseObject(Class clazz, String string) {
JavaType javaType = getCollectionType(ArrayList.class, clazz);
try {
return (List) new ObjectMapper().readValue(string, javaType);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}
@Configuration
public class NacosConfig {
@Value("${nacos.address}")
private String address;
@Value("${nacos.namespace}")
private String namespace;
@Bean
public ConfigService nacosConfigService() throws Exception {
Properties properties = new Properties();
//nacos集群地址
properties.put(PropertyKeyConst.SERVER_ADDR,address);
//namespace为空即为public
properties.put(PropertyKeyConst.NAMESPACE,namespace);
return ConfigFactory.createConfigService(properties);
}
}
public final class NacosConfigUtil {
public static final String GROUP_ID = "SENTINEL_GROUP";
public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";
public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-rules";
public static final String SYSTEM_DATA_ID_POSTFIX = "-system-rules";
public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-flow-rules";
public static final String AUTHORITY_DATA_ID_POSTFIX = "-authority-rules";
public static final String DASHBOARD_POSTFIX = "-sentinel-dashboard";
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() {}
/**
*
* 将规则序列化成JSON文本,存储到Nacos server中
*
* @param configService nacos config service
* @param app 应用名称
* @param postfix 规则后缀 eg.NacosConfigUtil.FLOW_DATA_ID_POSTFIX
* @param rules 规则对象
* @throws NacosException 异常
*/
public static void setRuleStringToNacos(ConfigService configService, String app, String postfix, List rules) throws NacosException {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
List ruleForApp = rules.stream()
.map(rule -> {
RuleEntity rule1 = (RuleEntity) rule;
System.out.println(rule1.getClass());
Rule rule2 = rule1.toRule();
System.out.println(rule2.getClass());
return rule2;
})
.collect(Collectors.toList());
String dataId = genDataId(app, postfix);
/**
* 俩种存储只是入参不同,为了满足功能的实现,存入nacos后,会有俩个配置,以后继续完善
*/
// 存储,控制微服务使用,即可以起到拦截作用,但是由于无法显示到控制台
configService.publishConfig(
dataId,
NacosConfigUtil.GROUP_ID,
JSONUtils.toJSONString(ruleForApp)
);
// 存储,给控制台显示使用,由于数据太多,会出现转化异常,虽然可以提供控制台显示,但是无法对微服务进行保护
configService.publishConfig(
dataId + DASHBOARD_POSTFIX,
NacosConfigUtil.GROUP_ID,
JSONUtils.toJSONString(rules)
);
}
/**
*
* 从Nacos server中查询响应规则,并将其反序列化成对应Rule实体
*
* @param configService nacos config service
* @param appName 应用名称
* @param postfix 规则后缀 eg.NacosConfigUtil.FLOW_DATA_ID_POSTFIX
* @param clazz 类
* @param 泛型
* @return 规则对象列表
* @throws NacosException 异常
*/
public static List getRuleEntitiesFromNacos(ConfigService configService, String appName, String postfix, Class clazz) throws NacosException {
String rules = configService.getConfig(
genDataId(appName, postfix) + DASHBOARD_POSTFIX,
NacosConfigUtil.GROUP_ID,
3000
);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return JSONUtils.parseObject(clazz, rules);
}
private static String genDataId(String appName, String postfix) {
return appName + postfix;
}
}
@Component("authorityRuleNacosProvider")
public class AuthorityRuleNacosProvider implements DynamicRuleProvider> {
@Autowired
private ConfigService configService;
@Override
public List getRules(String appName) throws Exception {
return NacosConfigUtil.getRuleEntitiesFromNacos(
this.configService,
appName,
NacosConfigUtil.AUTHORITY_DATA_ID_POSTFIX,
AuthorityRuleEntity.class
);
}
}
@Component("authorityRuleNacosPublisher")
public class AuthorityRuleNacosPublisher implements DynamicRulePublisher> {
@Autowired
private ConfigService configService;
@Override
public void publish(String app, List rules) throws Exception {
NacosConfigUtil.setRuleStringToNacos(
this.configService,
app,
NacosConfigUtil.AUTHORITY_DATA_ID_POSTFIX,
rules
);
}
}
@Component("degradeRuleNacosProvider")
public class DegradeRuleNacosProvider implements DynamicRuleProvider> {
@Autowired
private ConfigService configService;
@Override
public List getRules(String appName) throws Exception {
return NacosConfigUtil.getRuleEntitiesFromNacos(
this.configService,
appName,
NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
DegradeRuleEntity.class
);
}
}
@Component("degradeRuleNacosPublisher")
public class DegradeRuleNacosPublisher implements DynamicRulePublisher> {
@Autowired
private ConfigService configService;
@Override
public void publish(String app, List rules) throws Exception {
NacosConfigUtil.setRuleStringToNacos(
this.configService,
app,
NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
rules
);
}
}
@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider> {
@Autowired
private ConfigService configService;
@Override
public List getRules(String appName) throws Exception {
return NacosConfigUtil.getRuleEntitiesFromNacos(
this.configService,
appName,
NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
FlowRuleEntity.class
);
}
}
@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher> {
@Autowired
private ConfigService configService;
@Override
public void publish(String app, List rules) throws Exception {
NacosConfigUtil.setRuleStringToNacos(
this.configService,
app,
NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
rules
);
}
}
@Component("paramFlowRuleNacosProvider")
public class ParamFlowRuleNacosProvider implements DynamicRuleProvider> {
@Autowired
private ConfigService configService;
@Override
public List getRules(String appName) throws Exception {
return NacosConfigUtil.getRuleEntitiesFromNacos(
this.configService,
appName,
NacosConfigUtil.PARAM_FLOW_DATA_ID_POSTFIX,
ParamFlowRuleEntity.class
);
}
}
@Component("paramFlowRuleNacosPublisher")
public class ParamFlowRuleNacosPublisher implements DynamicRulePublisher> {
@Autowired
private ConfigService configService;
@Override
public void publish(String app, List rules) throws Exception {
NacosConfigUtil.setRuleStringToNacos(
this.configService,
app,
NacosConfigUtil.PARAM_FLOW_DATA_ID_POSTFIX,
rules
);
}
}
@Component("systemRuleNacosProvider")
public class SystemRuleNacosProvider implements DynamicRuleProvider> {
@Autowired
private ConfigService configService;
@Override
public List getRules(String appName) throws Exception {
return NacosConfigUtil.getRuleEntitiesFromNacos(
this.configService,
appName,
NacosConfigUtil.SYSTEM_DATA_ID_POSTFIX,
SystemRuleEntity.class
);
}
}
@Component("systemRuleNacosPublisher")
public class SystemRuleNacosPublisher implements DynamicRulePublisher> {
@Autowired
private ConfigService configService;
@Override
public void publish(String app, List rules) throws Exception {
NacosConfigUtil.setRuleStringToNacos(
this.configService,
app,
NacosConfigUtil.SYSTEM_DATA_ID_POSTFIX,
rules
);
}
}
@RestController
@RequestMapping(value = "/authority")
public class AuthorityRuleController {
private final Logger logger = LoggerFactory.getLogger(AuthorityRuleController.class);
@Autowired
@Qualifier("authorityRuleNacosProvider")
private DynamicRuleProvider> ruleProvider;
@Autowired
@Qualifier("authorityRuleNacosPublisher")
private DynamicRulePublisher> rulePublisher;
/* @Autowired
private SentinelApiClient sentinelApiClient;*/
@Autowired
private RuleRepository repository;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result> 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");
}
try {
// List rules = sentinelApiClient.fetchAuthorityRulesOfMachine(app, ip, port);
List 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 Result 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 apiAddAuthorityRule(@RequestBody AuthorityRuleEntity entity) {
Result 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);
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result apiUpdateParamFlowRule(@PathVariable("id") Long id,
@RequestBody AuthorityRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
Result 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");
}
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to save authority rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result apiDeleteRule(@PathVariable("id") Long id) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
AuthorityRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
publishRules(oldEntity.getApp());
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
return Result.ofSuccess(id);
}
/* private boolean publishRules(String app, String ip, Integer port) {
List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setAuthorityRuleOfMachine(app, ip, port, rules);
}*/
private void publishRules(String app) throws Exception {
List rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
@Controller
@RequestMapping(value = "/degrade", produces = MediaType.APPLICATION_JSON_VALUE)
public class DegradeController {
private final Logger logger = LoggerFactory.getLogger(DegradeController.class);
@Autowired
private InMemDegradeRuleStore repository;
@Autowired
@Qualifier("degradeRuleNacosProvider")
private DynamicRuleProvider> ruleProvider;
@Autowired
@Qualifier("degradeRuleNacosPublisher")
private DynamicRulePublisher> rulePublisher;
/*@Autowired
private SentinelApiClient sentinelApiClient;*/
@ResponseBody
@RequestMapping("/rules.json")
@AuthAction(PrivilegeType.READ_RULE)
public Result> queryMachineRules(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 rules = sentinelApiClient.fetchDegradeRuleOfMachine(app, ip, port);
//去nacos中取数据
List 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);
}
}
@ResponseBody
@RequestMapping("/new.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result add(String app, String ip, Integer port, String limitApp, String resource,
Double count, Integer timeWindow, Integer grade) {
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(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 (StringUtil.isBlank(limitApp)) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(resource)) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (count == null) {
return Result.ofFail(-1, "count can't be null");
}
if (timeWindow == null) {
return Result.ofFail(-1, "timeWindow can't be null");
}
if (grade == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
return Result.ofFail(-1, "Invalid grade: " + grade);
}
DegradeRuleEntity entity = new DegradeRuleEntity();
entity.setApp(app.trim());
entity.setIp(ip.trim());
entity.setPort(port);
entity.setLimitApp(limitApp.trim());
entity.setResource(resource.trim());
entity.setCount(count);
entity.setTimeWindow(timeWindow);
entity.setGrade(grade);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
//推送信息
publishRules(app);
} catch (Throwable throwable) {
logger.error("add error:", throwable);
return Result.ofThrowable(-1, throwable);
}
/* if (!publishRules(app, ip, port)) {
logger.info("publish degrade rules fail after rule add");
}*/
return Result.ofSuccess(entity);
}
@ResponseBody
@RequestMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result updateIfNotNull(Long id, String app, String limitApp, String resource,
Double count, Integer timeWindow, Integer grade) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
if (grade != null) {
if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
return Result.ofFail(-1, "Invalid grade: " + grade);
}
}
DegradeRuleEntity 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 (count != null) {
entity.setCount(count);
}
if (timeWindow != null) {
entity.setTimeWindow(timeWindow);
}
if (grade != null) {
entity.setGrade(grade);
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
//推送规则
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("save error:", throwable);
return Result.ofThrowable(-1, throwable);
}
/* if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("publish degrade rules fail after rule update");
}*/
return Result.ofSuccess(entity);
}
@ResponseBody
@RequestMapping("/delete.json")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result delete(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);
//推送规则
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("delete error:", throwable);
return Result.ofThrowable(-1, throwable);
}
/* if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.info("publish degrade rules fail after rule delete");
}*/
return Result.ofSuccess(id);
}
/* private boolean publishRules(String app, String ip, Integer port) {
List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules);
}*/
private void publishRules(String app) throws Exception {
List rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);
@Autowired
private InMemoryRuleRepositoryAdapter repository;
/* @Autowired
private SentinelApiClient sentinelApiClient;*/
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher> rulePublisher;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result> apiQueryMachineRules( @RequestParam String app) {
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 rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port);
List 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 Result 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.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null) {
return Result.ofFail(-1, "port can't be null");
}
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(PrivilegeType.WRITE_RULE)
public Result apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result 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 apiUpdateFlowRule(@PathVariable("id") Long id,
@RequestBody FlowRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
entity.setApp(oldEntity.getApp());
entity.setIp(oldEntity.getIp());
entity.setPort(oldEntity.getPort());
Result checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail");
}
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to update flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/delete.json")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result apiDeleteFlowRule(Long id) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
publishRules(oldEntity.getApp());
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
return Result.ofSuccess(id);
}
/* private CompletableFuture publishRules(String app, String ip, Integer port) {
List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
}*/
private void publishRules(/*@NonNull*/ String app) throws Exception {
List rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {
private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);
/* @Autowired
private SentinelApiClient sentinelApiClient;*/
@Autowired
@Qualifier("paramFlowRuleNacosProvider")
private DynamicRuleProvider> ruleProvider;
@Autowired
@Qualifier("paramFlowRuleNacosPublisher")
private DynamicRulePublisher> rulePublisher;
@Autowired
private AppManagement appManagement;
@Autowired
private RuleRepository repository;
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> 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 (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
/* return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
.thenApply(repository::saveAll)
.thenApply(Result::ofSuccess)
.get();*/
List 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 apiAddParamFlowRule(@RequestBody ParamFlowRuleEntity entity) {
Result 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 Result 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 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 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 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 publishRules(String app, String ip, Integer port) {
List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
}*/
private void publishRules(String app) throws Exception {
List rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
private Result 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);
}
@RestController
@RequestMapping("/system")
public class SystemController {
private final Logger logger = LoggerFactory.getLogger(SystemController.class);
@Autowired
private RuleRepository repository;
@Autowired
@Qualifier("systemRuleNacosProvider")
private DynamicRuleProvider> ruleProvider;
@Autowired
@Qualifier("systemRuleNacosPublisher")
private DynamicRulePublisher> rulePublisher;
/* @Autowired
private SentinelApiClient sentinelApiClient;*/
private Result 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 (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> apiQueryMachineRules(String app, String ip,
Integer port) {
Result> checkResult = checkBasicParams(app, ip, port);
if (checkResult != null) {
return checkResult;
}
try {
// List rules = sentinelApiClient.fetchSystemRuleOfMachine(app, ip, port);
List 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 apiAdd(String app, String ip, Integer port,
Double highestSystemLoad, Double highestCpuUsage, Long avgRt,
Long maxThread, Double qps) {
Result 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);
//推送
publishRules(app);
} 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");
}*/
return Result.ofSuccess(entity);
}
@GetMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result 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);
//推送
publishRules(entity.getApp());
} 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");
}*/
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);
//推送
publishRules(oldEntity.getApp());
} 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");
}*/
return Result.ofSuccess(id);
}
/* private boolean publishRules(String app, String ip, Integer port) {
List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setSystemRuleOfMachine(app, ip, port, rules);
}*/
private void publishRules(String app) throws Exception {
List rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
控制台改造完成
Sentinel中加入规则