有两种方式可以实现:
Springboot客户端做相应配置(推荐)
修改sentinel-dashboard的源码
一、Springboot客户端做相应配置(推荐)
1、添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
这里面已经实现了从nacos读取配置的方法,我们只需实现写入的方法即可
2、新建NacosWritableDataSource
利用 nacos 的 com.alibaba.nacos.api.config.ConfigService#publishConfig(java.lang.String, java.lang.String, java.lang.String) 方法,既会修改持久化文件,也会同步到对应的微服务
@Slf4j
public class NacosWritableDataSource<T> implements WritableDataSource<T> {
private NacosDataSourceProperties nacosDataSourceProperties;
private ConfigService configService;
private final Converter<T, String> configEncoder;
private final Lock lock = new ReentrantLock(true);
public NacosWritableDataSource(NacosDataSourceProperties nacosDataSourceProperties,
Converter<T, String> configEncoder) {
this.nacosDataSourceProperties = nacosDataSourceProperties;
this.configEncoder = configEncoder;
// 初始化 nacos configService
initConfigService();
}
private void initConfigService() {
try {
this.configService = NacosFactory.createConfigService(buildProperties(nacosDataSourceProperties));
} catch (NacosException e) {
log.error("init nacos configService error", e);
}
}
private Properties buildProperties(NacosDataSourceProperties nacosDataSourceProperties) {
Properties properties = new Properties();
if (StringUtils.hasLength(nacosDataSourceProperties.getServerAddr())) {
properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDataSourceProperties.getServerAddr());
} else {
properties.setProperty(PropertyKeyConst.ACCESS_KEY, nacosDataSourceProperties.getAccessKey());
properties.setProperty(PropertyKeyConst.SECRET_KEY, nacosDataSourceProperties.getSecretKey());
properties.setProperty(PropertyKeyConst.ENDPOINT, nacosDataSourceProperties.getEndpoint());
}
if (StringUtils.hasLength(nacosDataSourceProperties.getNamespace())) {
properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDataSourceProperties.getNamespace());
}
if (StringUtils.hasLength(nacosDataSourceProperties.getUsername())) {
properties.setProperty(PropertyKeyConst.USERNAME, nacosDataSourceProperties.getUsername());
}
if (StringUtils.hasLength(nacosDataSourceProperties.getPassword())) {
properties.setProperty(PropertyKeyConst.PASSWORD, nacosDataSourceProperties.getPassword());
}
return properties;
}
@Override
public void write(T value) throws Exception {
lock.lock();
try {
// 发布新配置
configService.publishConfig(nacosDataSourceProperties.getDataId(), nacosDataSourceProperties.getGroupId(),
this.configEncoder.convert(value), ConfigType.JSON.getType());
} finally {
lock.unlock();
}
}
@Override
public void close() throws Exception {
}
}
3、新建SentinelNacosDataSourceHandler
public class SentinelNacosDataSourceHandler implements SmartInitializingSingleton {
private final SentinelProperties sentinelProperties;
public SentinelNacosDataSourceHandler(SentinelProperties sentinelProperties) {
this.sentinelProperties = sentinelProperties;
}
// 实现SmartInitializingSingleton 的接口后,当所有非懒加载的单例Bean 都初始化完成以后,Spring 的IOC 容器会调用该接口的 afterSingletonsInstantiated() 方法
@Override
public void afterSingletonsInstantiated() {
sentinelProperties.getDatasource().values().forEach(this::registryWriter);
}
private void registryWriter(DataSourcePropertiesConfiguration dataSourceProperties) {
final NacosDataSourceProperties nacosDataSourceProperties = dataSourceProperties.getNacos();
if (nacosDataSourceProperties == null) {
return;
}
final RuleType ruleType = nacosDataSourceProperties.getRuleType();
// 通过数据源配置的 ruleType 来注册数据源
switch (ruleType) {
case FLOW:
WritableDataSource<List<FlowRule>> flowRuleWriter =
new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWriter);
break;
case DEGRADE:
WritableDataSource<List<DegradeRule>> degradeRuleWriter =
new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWriter);
break;
case PARAM_FLOW:
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWriter =
new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWriter);
break;
case SYSTEM:
WritableDataSource<List<SystemRule>> systemRuleWriter =
new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWriter);
break;
case AUTHORITY:
WritableDataSource<List<AuthorityRule>> authRuleWriter =
new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
WritableDataSourceRegistry.registerAuthorityDataSource(authRuleWriter);
break;
default:
break;
}
}
}
4、添加SentinelNacosDataSourceHandler管理
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(SentinelAutoConfiguration.class)
public class SentinelNacosDataSourceConfiguration {
@Bean
@ConditionalOnMissingBean
public SentinelNacosDataSourceHandler sentinelNacosDataSourceHandler(SentinelProperties sentinelProperties) {
return new SentinelNacosDataSourceHandler(sentinelProperties);
}
}
5、添加yml配置
spring:
cloud:
nacos:
sentinel:
transport:
dashboard: 192.168.252.212:11111
eager: true
web-context-unify: true #是否统一web上下文,默认true
datasource:
flow-manage: # 流控管理(这个名称可以自定义)
nacos:
namespace: sentinel
group-id: SENTINEL_GROUP
data-id: ${spring.application.name}-flow-rules
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
data-type: json
rule-type: flow # 指定文件配置的是那种规则
degrade-manage: # 熔断管理(这个名称可以自定义)
nacos:
namespace: sentinel
group-id: SENTINEL_GROUP
data-id: ${spring.application.name}-degrade-rules
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
data-type: json
rule-type: degrade
hot-param-manage: # 热点参数管理(这个名称可以自定义)
nacos:
namespace: sentinel
group-id: SENTINEL_GROUP
data-id: ${spring.application.name}-param-rules
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
data-type: json
rule-type: param-flow
现在在Sentinel Dashboard
中新建规则即可自动同步到nacos中
二、sentinel-dashboard源码修改:
1、在pom.xml中把sentinel-datasource-nacos依赖的scope
注释掉。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!--<scope>test</scope>-->
</dependency>
二、流控规则
1、在com.alibaba.csp.sentinel.dashboard.rule
目录下创建一个nacos
目录,并将test包下的 FlowRuleNacosProvider、FlowRuleNacosPublisher、NacosConfig 和 NacosConfigUtils都粘贴到nacos
目录
2、新增NacosPropertiesConfiguration配置类
@Configuration
@ConfigurationProperties(prefix = "sentinel.nacos")
public class NacosPropertiesConfiguration {
private String serverAddr;
private String dataId;
private String groupId;
private String namespace;
private String username;
private String password;
public String getServerAddr() {
return serverAddr;
}
public void setServerAddr(String serverAddr) {
this.serverAddr = serverAddr;
}
public String getDataId() {
return dataId;
}
public void setDataId(String dataId) {
this.dataId = dataId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
3、application.properties添加naocs配置
sentinel.nacos.serverAddr=******
sentinel.nacos.username=******
sentinel.nacos.password=******
sentinel.nacos.namespace=sentinel
4、修改 NacosConfig 类以支持自定义配置
// @Bean
// public ConfigService nacosConfigService() throws Exception {
// return ConfigFactory.createConfigService("localhost");
// }
@Bean
public ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws Exception {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
properties.put(PropertyKeyConst.USERNAME, nacosPropertiesConfiguration.getUsername());
properties.put(PropertyKeyConst.PASSWORD, nacosPropertiesConfiguration.getPassword());
return ConfigFactory.createConfigService(properties);
}
5、修改 FlowRuleNacosPublisher 确认配置文件格式为JSON
6、配置 V2 版本 Controller 调用 Nacos 提供的服务层
7、前端页面源码修改
文件路径: src/main/webapp/resources/app/scripts/controllers/identity.js
操作: 将 FlowServiceV1 改为 FlowServiceV2,将 /dashboard/flow/ 改为 /dashboard/v2/flow/。
文件路径: src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
操作: 搜索 dashboard.flowV1
并定位到第 57 行,去掉 V1
。
文件路径: src/main/webapp/resources/app/views/flow_v2.html
操作: 注释掉回到单机页面的按钮。
三、熔断规则
新增DegradeRuleNacosProvider:
@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.DEFRADE_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
新增DegradeRuleNacosPublisher:
@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.DEFRADE_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, converter.convert(rules), ConfigType.JSON.getType());
}
}
NacosConfigUtil类新增:
public static final String DEFRADE_DATA_ID_POSTFIX = "-degrade-rules";
修改DegradeController:
@RestController
@RequestMapping("/degrade")
public class DegradeController {
private final Logger logger = LoggerFactory.getLogger(DegradeController.class);
@Autowired
private RuleRepository<DegradeRuleEntity, Long> repository;
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;
@Autowired
@Qualifier("degradeRuleNacosProvider")
private DynamicRuleProvider<List<DegradeRuleEntity>> ruleProvider;
@Autowired
@Qualifier("degradeRuleNacosPublisher")
private DynamicRulePublisher<List<DegradeRuleEntity>> rulePublisher;
@GetMapping("/rules.json")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<DegradeRuleEntity>> apiQueryMachineRules(String app, String ip, Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (port == null) {
return Result.ofFail(-1, "port can't be null");
}
if (!appManagement.isValidMachineOfApp(app, ip)) {
return Result.ofFail(-1, "given ip does not belong to given app");
}
try {
//List<DegradeRuleEntity> rules = sentinelApiClient.fetchDegradeRuleOfMachine(app, ip, port);
List<DegradeRuleEntity> rules = ruleProvider.getRules(app);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("queryApps error:", throwable);
return Result.ofThrowable(-1, throwable);
}
}
@PostMapping("/rule")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<DegradeRuleEntity> apiAddRule(@RequestBody DegradeRuleEntity entity) {
Result<DegradeRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
publishRules(entity.getApp());
} catch (Throwable t) {
logger.error("Failed to add new degrade rule, app={}, ip={}", entity.getApp(), entity.getIp(), t);
return Result.ofThrowable(-1, t);
}
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);
publishRules(entity.getApp());
} catch (Throwable t) {
logger.error("Failed to save degrade rule, id={}, rule={}", id, entity, t);
return Result.ofThrowable(-1, t);
}
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);
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to delete degrade rule, id={}", id, throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(id);
}
// private boolean publishRules(String app, String ip, Integer port) {
// List<DegradeRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
// return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules);
// }
private void publishRules(/*@NonNull*/ String app) throws Exception {
List<DegradeRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
private <R> Result<R> checkEntityInternal(DegradeRuleEntity entity) {
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be blank");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (!appManagement.isValidMachineOfApp(entity.getApp(), entity.getIp())) {
return Result.ofFail(-1, "given ip does not belong to given app");
}
if (entity.getPort() == null || entity.getPort() <= 0) {
return Result.ofFail(-1, "invalid port: " + entity.getPort());
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
Double threshold = entity.getCount();
if (threshold == null || threshold < 0) {
return Result.ofFail(-1, "invalid threshold: " + threshold);
}
Integer recoveryTimeoutSec = entity.getTimeWindow();
if (recoveryTimeoutSec == null || recoveryTimeoutSec <= 0) {
return Result.ofFail(-1, "recoveryTimeout should be positive");
}
Integer strategy = entity.getGrade();
if (strategy == null) {
return Result.ofFail(-1, "circuit breaker strategy cannot be null");
}
if (strategy < CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()
|| strategy > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
return Result.ofFail(-1, "Invalid circuit breaker strategy: " + strategy);
}
if (entity.getMinRequestAmount() == null || entity.getMinRequestAmount() <= 0) {
return Result.ofFail(-1, "Invalid minRequestAmount");
}
if (entity.getStatIntervalMs() == null || entity.getStatIntervalMs() <= 0) {
return Result.ofFail(-1, "Invalid statInterval");
}
if (strategy == RuleConstant.DEGRADE_GRADE_RT) {
Double slowRatio = entity.getSlowRatioThreshold();
if (slowRatio == null) {
return Result.ofFail(-1, "SlowRatioThreshold is required for slow request ratio strategy");
} else if (slowRatio < 0 || slowRatio > 1) {
return Result.ofFail(-1, "SlowRatioThreshold should be in range: [0.0, 1.0]");
}
} else if (strategy == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
if (threshold > 1) {
return Result.ofFail(-1, "Ratio threshold should be in range: [0.0, 1.0]");
}
}
return null;
}
}
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);
}
四、热点参数
新增ParamRuleNacosProvider
@Component("paramRuleNacosProvider")
public class ParamRuleNacosProvider implements DynamicRuleProvider<List<ParamFlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<ParamFlowRuleEntity>> converter;
@Override
public List<ParamFlowRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.PARAM_FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
新增ParamRuleNacosPublisher
@Component("paramRuleNacosPublisher")
public class ParamRuleNacosPublisher implements DynamicRulePublisher<List<ParamFlowRuleEntity>> {
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<ParamFlowRuleEntity>, String> converter;
@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,
NacosConfigUtil.GROUP_ID, converter.convert(rules), ConfigType.JSON.getType());
}
}
NacosConfigUtil类新增:
public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules";
修改ParamFlowRuleController
@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("paramRuleNacosProvider")
private DynamicRuleProvider<List<ParamFlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("paramRuleNacosPublisher")
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());
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());
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());
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);
}
NacosConfig类新增:
@Bean
public Converter<List<ParamFlowRuleEntity>, String> paramRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<ParamFlowRuleEntity>> paramRuleEntityDecoder() {
return s -> JSON.parseArray(s, ParamFlowRuleEntity.class);
}
五、客户端配置
pom添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
yml文件添加配置
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
eager: true
web-context-unify: true #是否统一web上下文,默认true
datasource:
flow-manage: # 流控管理(这个名称可以自定义)
nacos:
namespace: sentinel
group-id: SENTINEL_GROUP
data-id: ${spring.application.name}-flow-rules
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
data-type: json
rule-type: flow # 指定文件配置的是那种规则
degrade-manage: # 熔断管理(这个名称可以自定义)
nacos:
namespace: sentinel
group-id: SENTINEL_GROUP
data-id: ${spring.application.name}-degrade-rules
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
data-type: json
rule-type: degrade
hot-param-manage: # 热点参数管理(这个名称可以自定义)
nacos:
namespace: sentinel
group-id: SENTINEL_GROUP
data-id: ${spring.application.name}-param-rules
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
data-type: json
rule-type: param-flow