前言
最近在体验Spring Cloud Alibaba,其中用到了Sentinel组件提供限流熔断,但限流规则默认存储在内存中,如果重启了客户端,限流规则就会消失。
官方虽然支持多种持久化方式,但并没有实现出来,还需要自己改造控制台,网上搜了下改造的教程,结果要么基本上是抄的,要么缺这缺那,实属难受,所以记录下自己实际操作的过程,方便以后使用。
Sentinel规则推送有三种,生产环境中还是推荐使用push模式,这篇文章也是按照push模式操作的。
本质上就是操作控制台,控制台将规则推送到远程配置中心,再推送到客户端,如果客户端重启就会从远程配置中心拉取规则并初始化。贴一张官方的图,很好理解:
因为正在尝试使用Spring Cloud Alibaba组件,并且也上了Nacos作为项目的注册中心与配置中心,所以考虑用Nacos来作为Sentinel规则的远程配置中心。
懒人版本
我把改造后的代码放在GitHub上了,链接:https://github.com/visionki/Sentinel-Nacos,拉取下来后修改修改sentinel-dashboard
模块下的application.properties
中Nacos相关配置,编译运行即可。
相关环境
组件 | 版本 |
---|---|
JDK | 1.8 |
Nacos控制台 | 1.4.1 |
Sentinel控制台 | 1.8.0 |
spring-cloud | Hoxton.SR8 |
spring-cloud-alibaba | 2.2.3.RELEASE |
控制台改造
下载Sentinel 1.8.0 源码
前往releases页面下载:https://github.com/alibaba/Sentinel/releases
解压后在idea打开,更新maven依赖,得到这么一个工程,sentinel-dashboard
模块是此次的主角,我们的改造都是在这个模块内进行的。
修改pom.xml
打开sentinel-dashboard
模块的pom.xml文件,找到sentinel-datasource-nacos
依赖,把test去掉:
拷贝限流规则示例代码
把src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos
文件夹拷贝到src/main/java/com/alibaba/csp/sentinel/dashboard/rule
目录下:
连接到Nacos
1、打开application.properties
文件,添加Nacos相关配置。
nacos.address=localhost:8848
# 如果要指定namespace就填入,不填默认是public。groupId默认值SENTINEL_GROUP。
nacos.namespace=dev
# 如果Nacos开启了鉴权,添加账号密码。
nacos.username=username
nacos.password=password
2、打开com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfig
类,引入刚刚填写的配置信息,如果Nacos没有开启鉴权则不需要username和password。
@Configuration
public class NacosConfig {
@Value("${nacos.address}")
private String address;
@Value("${nacos.namespace}")
private String namespace;
@Value("${nacos.username}")
private String username;
@Value("${nacos.password}")
private String password;
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
@Bean
public ConfigService nacosConfigService() throws Exception {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, address);
properties.put(PropertyKeyConst.NAMESPACE, namespace);
properties.put(PropertyKeyConst.USERNAME, username);
properties.put(PropertyKeyConst.PASSWORD, password);
return ConfigFactory.createConfigService(properties);
}
}
修改流控规则注入的bean
打开com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2
类,注入我们刚刚的限流规则类。
修改静态文件
1、打开src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
,找到dashboard.flowV1({app: entry.app})
修改为dashboard.flow({app: entry.app})
。
2、打开src/main/webapp/resources/app/scripts/controllers/identity.js
,把FlowServiceV1
改为FlowServiceV2
。
启动测试
至此控制台的限流规则部分已经改造完成,IDEA内直接启动DashboardApplication
类试试效果,再启动一个微服务连接到sentinel,访问一次微服务接口让sentinel控制台检测到路径。在控制台点击流控规则,右上角随便新增个限流规则,检查下Nacos中是否生成对应的配置文件即可。
直接在Nacos中修改出现的限流规则,看看有没有同步到Sentinel控制台。如果都能同步到,那就恭喜你成功了,到目前只做了限流规则的持久化,其他的还需要另外配置。
改造其他规则
设定配置文件
打开com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfigUtil
类,添加规则配置文件后缀。
public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-rules";
public static final String SYSTEM_DATA_ID_POSTFIX = "-system-rules";
public static final String AUTHORITY_DATA_ID_POSTFIX = "-authority-rules";
添加规则推送类
为了方便区分,我在nacos建了多几个文件夹。分别是authority(授权规则)
,degrade(降级规则)
,flow(流控规则)
,param(热点规则)
,system(系统规则)
,并把前面的限流规则移入对应的文件夹。
直接把FlowRuleNacosProvider
和FlowRuleNacosPublisher
复制一份到到degrade目录下,把Flow相关的类都改为Degrade的。
附上代码:
@Component("degradeRuleNacosProvider")
public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<DegradeRuleEntity>> converter;
@Override
public List<DegradeRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
@Component("degradeRuleNacosPublisher")
public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<DegradeRuleEntity>, String> converter;
@Override
public void publish(String app, List<DegradeRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, converter.convert(rules));
}
}
剩下三个规则和这个一样,分别在对应目录下创建类
authority(授权规则)的两个类:
AuthorityRuleNacosProvider
AuthorityRuleNacosPublisher
param(热点规则)的两个类:
ParamFlowRuleNacosProvider
ParamFlowRuleNacosPublisher
system(系统规则)的两个类:
SystemRuleNacosProvider
SystemRuleNacosPublisher
添加规则转换类
打开com.alibaba.csp.sentinel.dashboard.rule.nacos.NacosConfig
类,添加内容:
@Bean
public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() {
return s -> JSON.parseArray(s, DegradeRuleEntity.class);
}
@Bean
public Converter<List<SystemRuleEntity>, String> systemRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<SystemRuleEntity>> systemRuleEntityDecoder() {
return s -> JSON.parseArray(s, SystemRuleEntity.class);
}
@Bean
public Converter<List<AuthorityRuleEntity>, String> authorityRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<AuthorityRuleEntity>> authorityRuleEntityDecoder() {
return s -> JSON.parseArray(s, AuthorityRuleEntity.class);
}
@Bean
public Converter<List<ParamFlowRuleEntity>, String> paramFlowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<ParamFlowRuleEntity>> paramFlowRuleEntityDecoder() {
return s -> JSON.parseArray(s, ParamFlowRuleEntity.class);
}
修改降级规则控制器
com.alibaba.csp.sentinel.dashboard.controller.DegradeController
替换为以下内容:
@RestController
@RequestMapping("/degrade")
public class DegradeController {
private final Logger logger = LoggerFactory.getLogger(DegradeController.class);
@Autowired
private RuleRepository<DegradeRuleEntity, Long> repository;
/**-----修改开始-----**/
/*@Autowired
private SentinelApiClient sentinelApiClient;*/
@Autowired
@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) throws Exception {
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);
}
/**-----修改开始-----**/
/*if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.warn("Publish degrade rules failed, app={}", entity.getApp());
}*/
publishRules(entity.getApp());
/**-----修改结束-----**/
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<DegradeRuleEntity> apiUpdateRule(@PathVariable("id") Long id,
@RequestBody DegradeRuleEntity entity) throws Exception {
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);
}
/**-----修改开始-----**/
/*if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.warn("Publish degrade rules failed, app={}", entity.getApp());
}*/
publishRules(oldEntity.getApp());
/**-----修改结束-----**/
return Result.ofSuccess(entity);
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> delete(@PathVariable("id") Long id) throws Exception {
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);
}
/**-----修改开始-----**/
/*if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.warn("Publish degrade rules failed, app={}", oldEntity.getApp());
}*/
publishRules(oldEntity.getApp());
/**-----修改结束-----**/
return Result.ofSuccess(id);
}
/**-----修改开始-----**/
private void publishRules(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())) {
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;
}
}
修改授权规则控制器
com.alibaba.csp.sentinel.dashboard.controller.AuthorityRuleController
替换为以下内容:
@RestController
@RequestMapping(value = "/authority")
public class AuthorityRuleController {
private final Logger logger = LoggerFactory.getLogger(AuthorityRuleController.class);
/*@Autowired
private SentinelApiClient sentinelApiClient;*/
@Autowired
@Qualifier("authorityRuleNacosProvider")
private DynamicRuleProvider<List<AuthorityRuleEntity>> ruleProvider;
@Autowired
@Qualifier("authorityRuleNacosPublisher")
private DynamicRulePublisher<List<AuthorityRuleEntity>> rulePublisher;
@Autowired
private RuleRepository<AuthorityRuleEntity, Long> repository;
@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");
}*/
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) throws Exception {
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");
}*/
publishRules(entity.getApp());
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<AuthorityRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id,
@RequestBody AuthorityRuleEntity entity) throws Exception {
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");
}*/
publishRules(entity.getApp());
return Result.ofSuccess(entity);
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) throws Exception {
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");
}*/
publishRules(oldEntity.getApp());
return Result.ofSuccess(id);
}
private void publishRules(String app) throws Exception {
List<AuthorityRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app,rules);
}
}
修改热点规则控制器
com.alibaba.csp.sentinel.dashboard.controller.ParamFlowRuleController
替换为以下内容:
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {
private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);
/*@Autowired
private SentinelApiClient sentinelApiClient;*/
@Autowired
@Qualifier("paramFlowRuleNacosProvider")
private DynamicRuleProvider<List<ParamFlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("paramFlowRuleNacosPublisher")
private DynamicRulePublisher<List<ParamFlowRuleEntity>> rulePublisher;
@Autowired
private AppManagement appManagement;
@Autowired
private RuleRepository<ParamFlowRuleEntity, Long> 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<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 (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
List<ParamFlowRuleEntity> rules = ruleProvider.getRules(app);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
/*return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
.thenApply(repository::saveAll)
.thenApply(Result::ofSuccess)
.get();*/
} 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 void publishRules(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);
}
修改系统规则控制器
com.alibaba.csp.sentinel.dashboard.controller.SystemController
替换为以下内容:
@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
@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 (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) throws Exception {
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");
}*/
publishRules(app);
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) throws Exception {
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");
}*/
publishRules(entity.getApp());
return Result.ofSuccess(entity);
}
@RequestMapping("/delete.json")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<?> delete(Long id) throws Exception {
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");
}*/
publishRules(oldEntity.getApp());
return Result.ofSuccess(id);
}
private void publishRules(String app) throws Exception {
List<SystemRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
测试
至此已经全部改造完成了,和上面测试限流规则一样,对各个规则进行测试,如果能双向同步就代表能正常工作。我在Sentinel控制台上每个规则都创建一个,在Nacos能看到生成了这些文件:
一些注意事项
我测了下,在簇点链路
里添加限流规则,调用的是V1接口并非同步到远程配置中心接口,静态文件还需要进行其他的改造,自己解决。
微服务端
微服务端需要从配置中心获取各类规则,所以需要进行配置,首选是几个相关依赖:
<!-- 服务注册 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel的nacos持久化 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
配置文件中添加持久化的相关配置:
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: ${nacos.server-addr}
username: ${nacos.username}
password: ${nacos.password}
namespace: ${nacos.namespace}
groupId: SENTINEL_GROUP
dataId: ${spring.application.name}-flow-rules
rule-type: flow
degrade:
nacos:
server-addr: ${nacos.server-addr}
username: ${nacos.username}
password: ${nacos.password}
namespace: ${nacos.namespace}
groupId: SENTINEL_GROUP
dataId: ${spring.application.name}-degrade-rules
rule-type: degrade
param-flow:
nacos:
server-addr: ${nacos.server-addr}
username: ${nacos.username}
password: ${nacos.password}
namespace: ${nacos.namespace}
groupId: SENTINEL_GROUP
dataId: ${spring.application.name}-param-rules
rule-type: param-flow
system:
nacos:
server-addr: ${nacos.server-addr}
username: ${nacos.username}
password: ${nacos.password}
namespace: ${nacos.namespace}
groupId: SENTINEL_GROUP
dataId: ${spring.application.name}-system-rules
rule-type: system
authority:
nacos:
server-addr: ${nacos.server-addr}
username: ${nacos.username}
password: ${nacos.password}
namespace: ${nacos.namespace}
groupId: SENTINEL_GROUP
dataId: ${spring.application.name}-authority-rules
rule-type: authority
至此控制台和微服务都完成了Sentinel的push模式。
打包
前往根目录文件夹,在cmd命令执行mvn package -DskipTests
打包即可。
本文由 visionki 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jan 23, 2021 at 02:17 pm