Sentinel Dashboard-Nacos动态数据源配置

Sentinel Dashboard源码中支持push到Sentinel Client(SpringBoot)或动态数据源(Nacos, Zookeeper, Apollo),但目前默认是push到Sentinel Client,推到动态数据源需要稍微改造一下源码

Push模式#

img

配置#

  • 演示版本
    • 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规则

主要接口与配置类#

img

  • 拉取配置接口
    • FlowRuleApiProvider:Http拉取流量控制规则配置
    • FlowRuleNacosProvider:从Nacos拉取流量控制规则配置
    • ParamFlowRuleNacosProvider:需自行改造,从Nacos拉取热点参数规则配置

img

  • DynamicRulePublisher:推送配置接口
    • FlowRuleApiPublisher:Http推送流量控制规则配置
    • FlowRuleNacosPublisher:推送流量控制规则配置到Nacos
    • ParamFlowRuleNacosPublisher:需自行改造,推送热点参数规则配置到Nacos

  • 新建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

    img


  • 修改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();
        }
    }
    
    

参考:#

问题:#

  1. 关于集群token server独立部署高可用方案
posted @   FynnWang  阅读(455)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示