Sentinel Dashboard源码中支持push到Sentinel Client(SpringBoot)或动态数据源(Nacos, Zookeeper, Apollo),但目前默认是push到Sentinel Client,推到动态数据源需要稍微改造一下源码
Push模式
![img](https://images.cnblogs.com/cnblogs_com/wftop1/2188260/o_220823030545_image-20220823110507134.png)
配置
- 演示版本
- SpringBoot:2.2.2.RELEASE
- Sentinel-DashBoard:1.7.1
- Nacos-Server:1.2.0
SpringBoot配置
-
maven依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.0.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.7.1</version> </dependency>
-
yaml配置
spring: application: name: ti-dev profiles: active: dev cloud: # 注册到sentinel dashboard sentinel: transport: dashboard: localhost:8082 datasource: flow: # 名称随意, 标识流量控制 nacos: server-addr: localhost:8848 namespace: ${spring.profiles.active} groupId: SENTINEL_GROUP # sentinel dashboard推送的nacos配置的dataId生成规则 dataId: ${spring.application.name}-flow-rules # 规则类型,取值见: # org.springframework.cloud.alibaba.sentinel.datasource.RuleType rule-type: flow param-flow: # 名称随意, 标识热点参数 nacos: server-addr: localhost:8848 namespace: ${spring.profiles.active} groupId: SENTINEL_GROUP # sentinel dashboard推送的nacos配置的dataId生成规则 dataId: ${spring.application.name}-param-rules # 规则类型,取值见: # org.springframework.cloud.alibaba.sentinel.datasource.RuleType rule-type: param-flow eager: true # 启动项目是自动注入到sentinel中 web-content-unify: false
Sentinel Dashboard改造
默认情况下,在Sentinel Dashboard控制台修改流控规则之后,经Sentinel Dashboard内部服务,通过Http调用Sentinel Client接口同步rules规则
主要接口与配置类
- 拉取配置接口
- FlowRuleApiProvider:Http拉取
流量控制规则
配置 - FlowRuleNacosProvider:从Nacos拉取
流量控制规则
配置 - ParamFlowRuleNacosProvider:需自行改造,从Nacos拉取
热点参数规则
配置
- FlowRuleApiProvider:Http拉取
- DynamicRulePublisher:推送配置接口
- FlowRuleApiPublisher:Http推送
流量控制规则
配置 - FlowRuleNacosPublisher:推送
流量控制规则
配置到Nacos - ParamFlowRuleNacosPublisher:需自行改造,推送
热点参数规则
配置到Nacos
- FlowRuleApiPublisher:Http推送
-
新建ParamFlowRuleNacosProvider
package com.alibaba.csp.sentinel.dashboard.rule; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ @Component("paramFlowRuleNacosProvider") public class ParamFlowRuleNacosProvider 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); } }
-
新建ParamFlowRuleNacosPublisher
package com.alibaba.csp.sentinel.dashboard.rule; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ @Component("paramFlowRuleNacosPublisher") public class ParamFlowRuleNacosPublisher 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)); } }
-
修改NacosConfig
package com.alibaba.csp.sentinel.dashboard.rule; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.fastjson.JSON; import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.config.ConfigFactory; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; import java.util.Properties; /** * @author Eric Zhao * @since 1.4.0 */ @Configuration public class NacosConfig { @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 Converter<List<ParamFlowRuleEntity>, String> paramFlowRuleEntityEncoder() { return JSON::toJSONString; } // 热点参数规则转换器 @Bean public Converter<String, List<ParamFlowRuleEntity>> paramFlowRuleEntityDecoder() { return s -> JSON.parseArray(s, ParamFlowRuleEntity.class); } @Bean public ConfigService nacosConfigService() throws Exception { Properties properties = new Properties(); //nacos服务地址 properties.put(PropertyKeyConst.SERVER_ADDR, "localhost"); //nacos的namespace properties.put(PropertyKeyConst.NAMESPACE, "dev"); return ConfigFactory.createConfigService(properties); } }
-
新建com.alibaba.csp.sentinel.dashboard.controller.v2.ParamFlowRuleControllerV2
package com.alibaba.csp.sentinel.dashboard.controller.v2; import com.alibaba.csp.sentinel.dashboard.auth.AuthAction; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException; import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.dashboard.util.VersionUtils; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.*; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; /** * @author Eric Zhao * @since 0.2.1 */ @RestController @RequestMapping(value = "/v2/paramFlow") public class ParamFlowRuleControllerV2 { private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleControllerV2.class); @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); if (rules != null && !rules.isEmpty()) { for (ParamFlowRuleEntity entity : rules) { entity.setApp(app); if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) { entity.setId(entity.getClusterConfig().getFlowId()); } } } 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(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()); 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(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()); 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()); 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, String ip, Integer port) 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); }
-
修改Sentinel Dashboard前端调用的接口。找到resources\dist\js\app.js,搜索/paramFlow/rules,将/paramFlow/rules改为/v2/paramFlow/rules
-
修改ParamFlowRuleEntity
package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.fastjson.annotation.JSONField; import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.List; /** * @author Eric Zhao * @since 0.2.1 */ public class ParamFlowRuleEntity extends AbstractRuleEntity<ParamFlowRule> { public ParamFlowRuleEntity() { } public ParamFlowRuleEntity(ParamFlowRule rule) { AssertUtil.notNull(rule, "Authority rule should not be null"); this.rule = rule; } public static ParamFlowRuleEntity fromAuthorityRule(String app, String ip, Integer port, ParamFlowRule rule) { ParamFlowRuleEntity entity = new ParamFlowRuleEntity(rule); entity.setApp(app); entity.setIp(ip); entity.setPort(port); return entity; } // @JsonIgnore // @JSONField(serialize = false) public String getLimitApp() { return rule.getLimitApp(); } // @JsonIgnore // @JSONField(serialize = false) public String getResource() { return rule.getResource(); } // @JsonIgnore // @JSONField(serialize = false) public int getGrade() { return rule.getGrade(); } // @JsonIgnore // @JSONField(serialize = false) public Integer getParamIdx() { return rule.getParamIdx(); } // @JsonIgnore // @JSONField(serialize = false) public double getCount() { return rule.getCount(); } @JsonIgnore @JSONField(serialize = false) public List<ParamFlowItem> getParamFlowItemList() { return rule.getParamFlowItemList(); } // @JsonIgnore // @JSONField(serialize = false) public int getControlBehavior() { return rule.getControlBehavior(); } // @JsonIgnore // @JSONField(serialize = false) public int getMaxQueueingTimeMs() { return rule.getMaxQueueingTimeMs(); } // @JsonIgnore // @JSONField(serialize = false) public int getBurstCount() { return rule.getBurstCount(); } // @JsonIgnore // @JSONField(serialize = false) public long getDurationInSec() { return rule.getDurationInSec(); } // @JsonIgnore // @JSONField(serialize = false) public boolean isClusterMode() { return rule.isClusterMode(); } // @JsonIgnore // @JSONField(serialize = false) public ParamFlowClusterConfig getClusterConfig() { return rule.getClusterConfig(); } }
参考:
- Sentinel
- sentinel-awesome
- 阿里巴巴开源限流降级神器Sentinel大规模生产级应用实践
- 阿里限流神器Sentinel夺命连环 17 问?
- 令牌桶、漏斗、冷启动限流在sentinel的应用
- 使用Nacos存储Sentinel的限流规则
- SpringCloud实践:Sentinel流控
- Sentinel 与 Hystrix 的对比
- 基于信号量和令牌桶算法的限流
- 【开源】Sentinel高性能高可用集群限流解决方案
- Sentinel-基于Curator、Apollo实现高可用独立模式TokenServer部署下的集群限流
- 毕业版本依赖关系(推荐使用)
问题:
- 关于集群token server独立部署高可用方案