• SpringCloud Gateway基于nacos实现动态路由


    动态路由背景

    在使用 Cloud Gateway 的时候,官方文档提供的方案总是基于配置文件配置的方式

    @SpringBootApplication
    public class DemogatewayApplication {
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
            return builder.routes()
                .route("path_route", r -> r.path("/get")
                    .uri("http://httpbin.org"))
                .route("host_route", r -> r.host("*.myhost.org")
                    .uri("http://httpbin.org"))
                .route("rewrite_route", r -> r.host("*.rewrite.org")
                    .filters(f -> f.rewritePath("/foo/(?.*)", "/${segment}"))
                    .uri("http://httpbin.org"))
                .route("hystrix_route", r -> r.host("*.hystrix.org")
                    .filters(f -> f.hystrix(c -> c.setName("slowcmd")))
                    .uri("http://httpbin.org"))
                .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
                    .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
                    .uri("http://httpbin.org"))
                .route("limit_route", r -> r
                    .host("*.limited.org").and().path("/anything/**")
                    .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
                    .uri("http://httpbin.org"))
                .build();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 配置文件方式
    spring:
      jmx:
        enabled: false
      cloud:
        gateway:
          default-filters:
          - PrefixPath=/httpbin
          - AddResponseHeader=X-Response-Default-Foo, Default-Bar
     
          routes:
          # =====================================
          # to run server
          # $ wscat --listen 9000
          # to run client
          # $ wscat --connect ws://localhost:8080/echo
          - id: websocket_test
            uri: ws://localhost:9000
            order: 9000
            predicates:
            - Path=/echo
          # =====================================
          - id: default_path_to_httpbin
            uri: ${test.uri}
            order: 10000
            predicates:
            - Path=/**
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    Spring Cloud Gateway作为微服务的入口,需要尽量避免重启,而现在配置更改需要重启服务不能满足实际生产过程中的动态刷新、实时变更的业务需求,所以我们需要在Spring Cloud Gateway运行时动态配置网关。

    我们明确了目标需要实现动态路由,那么实现动态路由的方案有很多种,这里拿三种常见的方案来说明下:

    前两种方案本质上是一种方案,只是数据存储方式不同,大体实现思路是这样,我们通过接口定义路由的增上改查接口,通过接口来修改路由信息,将修改后的数据存储到mysql或redis中,并刷新路由,达到动态更新的目的。

    第三种方案相对前两种相对简单,我们使用nacos的配置中心,将路由配置放在nacos上,写个监听器监听nacos上配置的变化,将变化后的配置更新到GateWay应用的进程内。

    我们下面采用第三种方案,因为网关未连接mysql,使用redis还有开发相应的api和对应的web,来配置路由信息,而我们目前没有开发web的需求,所以我们采用第三种方案。

    架构设计思路

    • 封装RouteOperator类,用来删除和增加gateway进程内的路由;
    • 创建一个配置类RouteOperatorConfig,可以将RouteOperator作为bean对象注册到Spring环境中;
    • 创建nacos配置监听器,监听nacos上配置变化信息,将变更的信息更新到进程中;

    架构图如下:
    在这里插入图片描述
    代码结构如下:
    在这里插入图片描述

    搭建过程

    • 父级pom依赖
     <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
            <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
            <spring-alibaba-cloud.version>2.2.5.RELEASE</spring-alibaba-cloud.version>
     </properties>
     
     <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>${spring-alibaba-cloud.version}</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • gateway依赖
            <!--nacosconfig-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
                <version>2.2.5.RELEASE</version>
            </dependency>
    
            <!--nacosdiscovery-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                <version>2.2.5.RELEASE</version>
            </dependency>
            
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
                <version>2.2.5.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>2.2.5.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-loadbalancer</artifactId>
                <version>2.2.0.RELEASE</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 创建启动类
    /**
     * @author code
     * @version 1.0
     * @Date 2022/9/15 16:39
     * @Description ${网关}
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class GatewayServerApp {
        public static void main( String[] args ) {
            //设置全局变量加密密钥
            System.setProperty("spring.cloud.nacos.username","nacos");
            System.setProperty("spring.cloud.nacos.password","nacos");
            SpringApplication.run(GatewayServerApp.class,args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 配置文件
    server:
      port: 7086
    spring:
      application:
        #应用名
        name: 
      cloud:
        nacos:
          config:
            #nacos地址
            server-addr: 
            name: ${spring.application.name}
            #命名空间的id
            namespace: 
            #配置文件的类型
            file-extension: yml
            #配置分组
            group: 
          discovery:
            #nacos地址
            server-addr: 
            #命名空间的id
            namespace: 
            group: 
        gateway:
          discovery:
            locator:
              enabled: true
    #      routes:
    #        - id: 
    #          uri: http://ip:port   lb:instance
    #          predicates:
    #            - Path=/oauth1/**
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 在文件里配置路由

    在这里插入图片描述

    • 启动报错

    在这里插入图片描述
    大致意思是在springboot整合gateway时, gateway组件中的 【spring-boot-starter-webflux】 和 springboot作为web项目启动必不可少的 【spring-boot-starter-web】 出现冲突
    我们按照提示: Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.

    在配置文件配置下 spring.main.web-application-type=reactive 就好了

    通过nacos配置动态路由

    路由配置的接口和实现

    • RouteService
    /**
     * @author code
     * @version 1.0
     * @Date 2022/9/15 17:17
     * @Description ${DESCRIPTION}
     */
    public interface RouteService {
    
        void update(RouteDefinition routeDefinition);
    
        void add(RouteDefinition routeDefinition);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • RouteServiceImpl
    
    /**
     * @author code
     * @version 1.0
     * @Date 2022/9/15 17:19
     * @Description ${DESCRIPTION}
     */
    @Service
    public class RouteServiceImpl implements RouteService,ApplicationEventPublisherAware {
        @Autowired
        private RouteDefinitionWriter routeDefinitionWriter;
    
        @Autowired
        private ApplicationEventPublisher publisher;
    
        @Override
        public void update(RouteDefinition routeDefinition){
            this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
            routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        }
    
        @Override
        public void add(RouteDefinition routeDefinition){
            routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        }
    
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher){
            this.publisher = applicationEventPublisher;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    其中:
    RouteDefinitionWriter:提供了对路由的增加删除等操作
    ApplicationEventPublisher: 是ApplicationContext的父接口之一,他的功能就是发布事件,也就是把某个事件告诉所有与这个事件相关的监听器

    在nacos创建gateway-routes配置文件

    在这里插入图片描述
    配置的路由如下:

    [
        {
            "predicates":[
                {
                    "args":{
                        "pattern":"/order/**"
                    },
                    "name":"Path"
                }
            ],
            "id":"mdx-shop-order",
            "filters":[
                {
                    "args":{
                        "parts":1
                    },
                    "name":"StripPrefix"
                }
            ],
            "uri":"lb://mdx-shop-order",
            "order":1
        }
    ]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这个路由配置对应的就是gateway中的RouteDefinition类

    在本地配置文件下配置路由的data-id和group和命名空间

    gateway:
      routes:
        config:
          data-id: gateway-routes  #动态路由
          group: shop
          namespace: mdx
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    格式类似于如下:

    server:
      port: 9010
    
    spring:
      application:
        name: mdx-shop-gateway
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
            namespace: mdx
            group: mdx
        gateway:
          discovery:
            locator:
              enabled: true  #开启通过服务中心的自动根据 serviceId 创建路由的功能
      main:
        web-application-type: reactive
    
    gateway:
      routes:
        config:
          data-id: gateway-routes  #动态路由
          group: shop
          namespace: mdx
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    创建相关的路由配置类

    • GatewayRouteConfigProperties
    /**
     * @author code
     * @version 1.0
     * @Date 2022/9/15 16:49
     * @Description ${配置类}
     */
    @ConfigurationProperties(prefix = "gateway.routes.config")
    @Component
    @Data
    public class GatewayRouteConfigProperties {
        private String dataId;
        private String group;
        private String namespace;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    实例化nacos的ConfigService,交由springbean管理

    ConfigService: 这个类是nacos的分布式配置接口,主要是用来获取配置和添加监听器
    由NacosFactory来创建ConfigService

    /**
     * @author code
     * @version 1.0
     * @Date 2022/9/15 17:04
     * @Description ${configService}
     */
    @Configuration
    public class GatewayConfigServiceConfig {
        @Autowired
        private GatewayRouteConfigProperties configProperties;
    
        @Autowired
        private NacosConfigProperties nacosConfigProperties;
    
        @Bean
        public ConfigService configService() throws NacosException{
            Properties properties = new Properties();
            properties.setProperty(PropertyKeyConst.SERVER_ADDR,nacosConfigProperties.getServerAddr());
            properties.setProperty(PropertyKeyConst.NAMESPACE,configProperties.getNamespace());
            properties.setProperty(PropertyKeyConst.USERNAME,"nacos");
            properties.setProperty(PropertyKeyConst.PASSWORD,"nacos");
            return NacosFactory.createConfigService(properties);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    动态路由主要实现

    项目启动时会加载这个类
    @PostConstruc 注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法

    /**
     * @author code
     * @version 1.0
     * @Date 2022/9/15 17:13
     * @Description ${动态路由}
     */
    @Component
    @RefreshScope
    public class GatewayRouteInitConfig {
        @Autowired
        private GatewayRouteConfigProperties configProperties;
    
        @Autowired
        private NacosConfigProperties nacosConfigProperties;
    
        @Autowired
        private RouteService routeService;
    
        @Autowired
        private ConfigService configService;
    
        private final ObjectMapper objectMapper = new ObjectMapper();
    
        @PostConstruct
        public void init(){
            try{
                String initConfigInfo = configService.getConfigAndSignListener(configProperties.getDataId(), configProperties.getGroup(), nacosConfigProperties.getTimeout(), new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }
    
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        if(!StringUtils.isEmpty(configInfo)){
                            List<RouteDefinition> routeDefinitions = null;
                            try{
                                routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
                                });
                            }catch (JsonProcessingException e){
                                e.printStackTrace();
                            }
                            for(RouteDefinition definition : Objects.requireNonNull(routeDefinitions)){
                                routeService.update(definition);
                            }
                        }else {
                            System.out.println("当前网关无动态路由配置");
                        }
                    }
                });
                if(!StringUtils.isEmpty(initConfigInfo)){
                    List<RouteDefinition> routeDefinitions = objectMapper.readValue(initConfigInfo, new TypeReference<List<RouteDefinition>>() {
                    });
                    for(RouteDefinition definition : Objects.requireNonNull(routeDefinitions)){
                        routeService.add(definition);
                    }
                }else {
                    System.out.println("当前网关无动态路由配置");
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    注意:要加==@RefreshScope==注解,实时更新路由配置

    问题

    1. web依赖冲突

    在这里插入图片描述
    我们按照提示: Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.
    在配置文件配置下 spring.main.web-application-type=reactive 就好了

    因为gateway是基于webflux

    1. invalid host

    应用名不支持下划线

    1. 路由转发503
      是因为使用的nacos,默认不支持ribbon,需要添加依赖
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>2.2.5.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-loadbalancer</artifactId>
                <version>2.2.0.RELEASE</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    问题

    java.util.ConcurrentModificationExceptioni ConcurrentModificationException null

  • 相关阅读:
    sqlmap --os-shell(写入木马获取getshell)
    官网子域名网站发布流程
    idea 2021.2.3版本中隐藏target和.iml文件问题的解决
    怎样做好一个开源项目
    数据一致性:核心概念与实现策略
    paddle ocr 训练数字识别模型
    《Linux运维篇:Linux系统运维指南》
    DataSheet专业名词解读——每天10个专业名词(1)23.9.18 (NXP)MPC5604B/C
    谨以此篇,纪念我2023年曲折的计算机保研之路
    解决caffe中的python环境安装的问题
  • 原文地址:https://blog.csdn.net/yy1209357299/article/details/126888944