• SpringCloud Alibaba 改造Sentinel Dashboard将流控规则持久化到Nacos


    Sentinel Dashboard集成Nacos目录:

    SpringCloud Alibaba 改造Sentinel Dashboard将流控规则持久化到Nacos  本文

    SpringCloud Alibaba 改造Sentinel Dashboard将熔断规则持久化到Nacos

    Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。经历过Alibaba历届双十一的考验,其性能的卓越性肯定是不言而喻的。

    Sentinel Dashboard是Sentinel提供的图形化控制台,可以通过Sentinel Dashboard维护流控规则、熔断规则、热点规则等。

    然而,开源版本的Sentinel Dashboard是无法直接应用于生产环境中的,这是因为通过开源版本的Sentinel Dashboard维护的各项规则是存储于内存中的,当Sentinel Dashboard重启,则内存中的各项规则也一并丢失,这在生产上是不被允许的。

    在Alibaba Sentinel的githup上,有一篇帖子在生产环境中使用Sentinel,介绍了Sentinel的三种规则管理和推送规则,

    如下,而开源版本的Sentinel Dashboard使用的就是其中的原始模式,可以看到是不被推荐在生产环境中应用的。

    而另外两种方式,pull模式和push模式,前者是使用诸如Nacos,利用Nacos Config的特性将Nacos作为纯粹的数据源来使用。当需要对流控规则做修改时,需要到Nacos上进行修改,然后Sentinel Dashboard拉取(Pull)Nacos Config上存储的规则并展示在Dashboard上。

    此种方式下,在Dashboard上维护的规则无法直接存储到Nacos上,修改规则需要到Nacos上进行,但这样的话就无法利用Dashboard提供的图形化界面,在Nacos上维护规则也容易出错,且对人员的要求高,需要非常熟悉规则配置的参数。同时拉取模式无法保证时效性。

    所以Sentinel官方推荐的方式是使用Push模式,通过Dashboard配置的规则直接可以存储在如Nacos之类的数据源上,客户端通过订阅Nacos上的配置来拉取规则配置,具体架构如官网上的下图:

    这种方式好是好,但是开源的Sentinel Dashboard并未提供实现,来将配置好的规则存储于第三方的数据源中,需要直接下载Sentinel Dashboard的源代码,依据选择的第三方数据源修改代码予以实现。本文下面的内容就是介绍如何实现Sentinel Dashboard将规则推送至Nacos上,以及各个应用如何订阅Nacos上的配置实现流控的

    SpringCloud、SpringCloud Alibaba和SpringBoot三者之间有比较严格的版本依赖,在Sentinel开源版本的使用中可能会遇到各种各样的问题,这其中绝大多数是由于三者版本不匹配导致的,因此建议使用官方推荐的版本。Sentinel官网上并未详细介绍三者版本之间的关系,而是需要到githup上才能得知具体的依赖关系,介绍的网址如下:

    SpringCloud Alibaba版本说明

    本文使用的是如下的版本:


    下载Sentinel Dashboard源代码

    下载地址如下:

    https://github.com/alibaba/Sentinel/,使用IDE打开sentinel-dashboard项目

    一. Sentinel Dashboard集成Nacos实现流控规则持久化

     Sentinel Dashboard的流控规则下的所有操作,都会调用com.alibaba.csp.sentinel.dashboard.controller.FlowControllerV1这个类,这个类中包含流控规则本地化(内存中)的CRUD操作,因此流控规则是存储在内存中的,所以每当重启Dashboard后,内存中的内容就会丢失。

    而com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2中同样实现了流控规则的CRUD,和V1版本不同的是,它可以实现指定数据源的规则拉取(从指定的数据源中查询已经配置好的流控规则)和发布(将通过Dashboard维护的内容存储到指定的数据源中)。

    FlowControllerV2中注入了两个非常重要的类:com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider和com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher分别实现了拉取和发布动作。

    这里就需要扩展这两个类,实现集成Nacos来实现Sentinel Dashboard规则的同步。

    1. 首先修改Sentinel Dashboard源码中的Pom文件,把sentinel-datasource-nacos依赖的<scope>注释掉

    <!-- for Nacos rule publisher sample -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
        <!-- 使用Nacos作为数据源,需要将scope=test注释掉 -->
        <!--<scope>test</scope>-->
    </dependency>

     2. 修改html文件,使得前台维护动作可以调用V2接口

    打开:/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html,按照截图中的介绍修改此html代码。

     

    打开:/sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html,注释掉<回到单机页面>代码。注释掉此段代码的原因是,当我们修改了sidebar.html中的代码注释掉了V1版本的页面,放开了V2版本的页面后,V2版本的页面依然保留了原有的在内存中维护规则的入口,就是这个<回到单机页面>按钮。

    通过点击<回到单机页面>按钮进入到的维护页面维护的规则依然是存储在内存中的,为了避免使用者的误解,故注释掉此段代码。

    接下来修改簇族链路中的流控规则添加按钮:

     打开:\src\main\webapp\resources\app\scripts\controllers\identity.js

    修改下图红框位置的代码,将FlowServiceV1修改为FlowServiceV2。

    修改前代码如下:

    修改后代码如下:

     

     3:创建DynamicRuleProvider和DynamicRulePublisher新的实现类

    3.1 创建静态类,定义所需常量

    /**
     * Nacos常量类
     * @author gang.wang
     * 2021年11月8日
     */
    public class NacosConstants {
        
        public static final String DATA_ID_POSTFIX = "-sentinel-flow";
        
        public static final String GROUP_ID = "DEFAULT_GROUP";
    
    }

    3.2 创建NacosPropertiesConfiguration类,加载application.properties中的Nacos配置

    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    /**
     * 加载Nacos配置
     * @author gang.wang
     * 2021年10月31日
     */
    @ConfigurationProperties(prefix="sentinel.nacos")
    public class NacosPropertiesConfiguration {
        
        /**
         * Nacos服务地址
         */
        private String serverAddr;
        
        private String dataId;
        
        private String groupId = "DEFAULT_GROUP";
        
        private String namespace;
    
        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;
        }
        
    }

    3.3 创建NacosConfiguration类,初始化Nacos配置

    import java.util.List;
    import java.util.Properties;
    
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
    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 com.alibaba.nacos.api.exception.NacosException;
    
    /**
     * Nacos配置类
     * @author gang.wang
     * 2021年10月31日
     */
    @EnableConfigurationProperties(NacosPropertiesConfiguration.class)
    @Configuration
    public class NacosConfiguration {
    
        @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(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws NacosException {
            Properties properties = new Properties();
            properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
            properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
            return ConfigFactory.createConfigService(properties);
        }
    }

    3.4 创建DynamicRuleProvider的实现类

    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
    import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
    import com.alibaba.csp.sentinel.datasource.Converter;
    import com.alibaba.nacos.api.config.ConfigService;
    
    /**
     * 实现从Nacos配置中心获取流控规则
     * @author gang.wang
     * 2021年11月8日
     */
    @Service
    public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
        
        private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosProvider.class);
        
        @Autowired
        private NacosPropertiesConfiguration nacosConfigProperties;
        
        @Autowired
        private ConfigService configService;
        
        @Autowired
        private Converter<String, List<FlowRuleEntity>> converter;
        
        @Override
        public List<FlowRuleEntity> getRules(String appName) throws Exception {
            
            //定义dataId 应用名+固定后缀
            String dataId = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
            
            String rules = configService.getConfig(dataId, nacosConfigProperties.getGroupId(), 3000);
            
            logger.info("Pull FlowRule from Nacos Config : {}", rules);
            
            if(StringUtils.isEmpty(rules)) {
                return new ArrayList<>();
            }
            return converter.convert(rules);
        }
    }

     3.5 创建DynamicRulePublisher的实现类

    import java.util.List;
    
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
    import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
    import com.alibaba.csp.sentinel.datasource.Converter;
    import com.alibaba.nacos.api.config.ConfigService;
    
    /**
     * 将通过Sentinel Dashboard上维护的流控规则数据持久化到Nacos中
     * @author gang.wang
     * 2021年11月8日
     */
    @Service
    public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
        
        private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosPublisher.class);
        
        @Autowired
        private NacosPropertiesConfiguration nacosConfigProperties;
        
        @Autowired
        private ConfigService configService;
        
        @Autowired
        private Converter<List<FlowRuleEntity>, String> converter;
    
        @Override
        public void publish(String appName, List<FlowRuleEntity> rules) throws Exception {
            
            if(StringUtils.isBlank(appName)) {
                logger.error("传入的AppName为Null");
                return ;
            }
            
            if(null == rules) {
                logger.error("传入的流控规则数据为null");
                return ;
            }
            
            String dataId = new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
            
            configService.publishConfig(dataId, nacosConfigProperties.getGroupId(), converter.convert(rules));
            
        }
    }

    4:修改FlowControllerV2中DynamicRuleProvider和DynamicRulePublisher的依赖注入

    修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2中DynamicRuleProvider和DynamicRulePublisher的依赖注入,引用最新的Nacos Provider和Publisher

    修改后如下:

    @Autowired
    //@Qualifier("flowRuleDefaultProvider") //注释掉原注入
    @Qualifier("flowRuleNacosProvider")//Nacos数据源
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
        
    /**
    * 动态规则的发布,将在Sentinel Dashboard中修改的规则同步到指定数据源中。
    */
    @Autowired
    //@Qualifier("flowRuleDefaultPublisher")
    @Qualifier("flowRuleNacosPublisher")//Nacos数据源
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

    5:添加Nacos配置信息

    修改application.properties文件的内容添加Nacos服务器配置。

    # 定义Nacos服务器信息
    sentinel.nacos.serverAddr=127.0.0.1:8848
    sentinel.nacos.namespace=37c7c263-bdf1-41db-9f34-bf1094111111
    sentinel.nacos.group-id=DEFAULT_GROUP

     6:重新打包并运行我们修改好的Sentinel Dashboard

     启动Sentinel Dashboard项目,浏览器中访问:http://127.0.0.1:8080/#/login  输入用户名/密码=sentinel/sentinel,就可以顺利登陆Sentinel Dashboard控制台了!

    此时进入控制台后,左边菜单中还看不到任何菜单信息。因为此刻还没有任何项目连接到Sentinel Dashboard上。

    7:创建SpringBoot应用并连接到Sentinel Dashboard上

    7.1 应用的Pom文件中添加sentinel和nacos相关依赖

    <?xml version="1.0"?>
    <project
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
        xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.alibaba.sentinel</groupId>
        <artifactId>sentinel-dashboard-test</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>sentinel-dashboard-test</name>
        <url>http://maven.apache.org</url>
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        <dependencies>
    
            <!-- Spring Cloud -->
    
            <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>
    
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
            
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-datasource-nacos</artifactId>
            </dependency>
    
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bootstrap</artifactId>
                <version>3.0.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
    
            <!-- Database -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            
            <dependency>
                 <groupId>com.oracle</groupId>
                 <artifactId>ojdbc7</artifactId>
                 <version>12.1.0.1</version>
             </dependency>
    
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.0</version>
            </dependency>
    
            <dependency>
                <groupId>com.zaxxer</groupId>
                <artifactId>HikariCP</artifactId>
            </dependency>
    
            <!-- Other -->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
    
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
            </dependency>
    
        </dependencies>
        
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    View Code

    7.2 修改应用的yml文件,添加Nacos和Sentinel Dashboard配置

    spring:
      application:
        name: sentinel-dashboard-test
      jackson:
        time-zone: GMT+8
        date-format: yyyy-MM-dd HH:mm:ss
      cloud:
        nacos:
          discovery:
            namespace: 37c7c263-bdf1-41db-9f34-bf10948be752
            server-addr: 127.0.0.1:8848
            weight: 1
          config:
            namespace: 37c7c263-bdf1-41db-9f34-bf10948be752
            file-extension: yml
            max-retry: 5
            name: sentinel-dashboard-test
            refresh-enabled: true
            prefix: 
        sentinel:
          transport: 
            dashboard: 127.0.0.1:8080
          datasource: 
            flow: 
              nacos: 
                server-addr: 127.0.0.1:8848
                namespace: 37c7c263-bdf1-41db-9f34-bf10948be752
                data-id: ${spring.application.name}-sentinel-flow
                group-id: DEFAULT_GROUP
                data-type: json
                rule-type: flow

     7.3 创建一个接口

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    
    /**
     * @author gang.wang
     * 2021年9月15日
     */
    @RestController
    public class SentinelDemoOneController {
        
        private Logger logger = LoggerFactory.getLogger(SentinelDemoOneController.class);
        
        @SentinelResource(value = "hello", blockHandler = "blockHandlerHello")
        @GetMapping("/say")
        public String hello() {
            return "hello, Gary!";
        }
    public String blockHandlerHello(BlockException ex) {
            logger.error("当前请求已被限流", ex);
            return "当前请求已被限流";
        }
    
    }

    7.4 启动应用并访问定义的接口

    启动应用,并使用如Postman访问定义好的/say接口。因为Sentinel Dashboard是使用懒加载的模式检测需要拦截的接口(http://127.0.0.1:8083/say),因此启动应用后如果不调用几次接口,则在Sentinel Dashboard中还是看不到我们定义好的接口信息。

    刷新Sentinel Dashboard页面,可以看到应用了

     点击实时监控菜单,可以看到刚才我们访问的接口的监控数据

     

    点击流控规则菜单,新建一条流控规则:

    这里我们为了验证方便,将qps的阈值设置为1,即每秒允许一个请求通过。

     8:验证流控规则是否生效

    再次使用Postman快速访问这个接口,可以看到有时候会成功,有时候会失败,失败时的返回结果如下:

    9:验证通过Sentinel Dashboard配置好的流控规则是否正确存储在Nacos上

    访问Nacos控制台,查看对应的Namespace下是否有dataId = sentinel-dashboard-test-sentinel-flow的配置。

    可见,通过Sentinel Dashboard配置的流控规则已经自动存储在Nacos中了!

     

    至此,Sentinel Dashboard与Nacos之间对于流控规则的同步已经完成了。

  • 相关阅读:
    JSP 页面中插入图片
    半角空格 全角空格 不间断空格 通过过滤器解决
    Eclispe 错误:找不到或无法加载加载主类
    Eclipse 新建 Maven web 项目
    Eclipse 中 Maven 项目默认JDK版本为1.5 的解决方法
    Eclipse Maven: Cannot change version of project facet Dynamic web to 3.0 的解决方法
    JSP 页面跳转中的参数传递
    Maven 基本用法
    JSP 页面跳转的实现方法
    Android开发入门(2)Java 基础
  • 原文地址:https://www.cnblogs.com/wanggangblog/p/15551420.html
Copyright © 2020-2023  润新知