zoukankan      html  css  js  c++  java
  • Nacos+Spring Cloud Gateway动态路由配置

    前言

      Nacos最近项目一直在使用,其简单灵活,支持更细粒度的命令空间,分组等为麻烦复杂的环境切换提供了方便;同时也很好支持动态路由的配置,只需要简单的几步即可。在国产的注册中心、配置中心中比较突出,容易上手,本文通过gateway、nacos-consumer、nacos-provider三个简单模块来展示:Nacos下动态路由配置。

      


    一、Nacos环境准备

    1、启动Nacos配置中心并创建路由配置

    具体的Nacos怎么配置就不介绍了,可以参考阿里巴巴的官方介绍,这里通过windows直接本地启动开启单机模式,登录Nacos Console,创建dev的namespace,在dev下的默认分组下创建gateway-router的dataId

    gateway-router的主要初始化配置如下:关于gateway的组成(id,order、predicates断言,uri)这里就不详细说明的了,可以自行百度下

    [{
        "id": "consumer-router",
        "order": 0,
        "predicates": [{
            "args": {
                "pattern": "/consume/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-consumer"
    },{
        "id": "provider-router",
        "order": 2,
        "predicates": [{
            "args": {
                "pattern": "/provide/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-provider"
    }]

    2、连接Nacos配置中心

    通常在项目中配置“配置中心”往往都是在bootstrap.propertis(yaml)中配置,这样才能保证项目中路由配置从Nacos Config中读取。

    # nacos配置中心配置建议在bootstrap.properties中配置
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    #spring.cloud.nacos.config.file-extension=properties
    # 配置中心的命名空间:dev 的命名空间(环境)
    spring.cloud.nacos.config.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea

    Application启动类中增加注解@EnableDiscoveryClient,才能保证连接到Nacos Config

    @SpringBootApplication
    @EnableDiscoveryClient
    public class GatewayApplication
    {
        public static void main( String[] args )
        {
            SpringApplication.run(GatewayApplication.class, args);
        }
    }

    二、项目构建

    1、项目结构

    创建简单的spring boot多模块结构,推荐使用idea创建

    1)Nacos父模块:

    <groupId>com.springcloud</groupId>
    <artifactId>nacos</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>nacos</name>
    <description>Nacos Demo</description>

    首先pom文件引入Spring Cloud Alibaba Nacos组件:注册中心nacos-discovery与配置中心nacos-config

     <!--nacos 客户端 注册中心-->
     <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      <version>${alibaba-nacos.version}</version>
     </dependency>
      <!--nacos 客户端 配置中心-->
     <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      <version>${alibaba-nacos.version}</version>
     </dependency>

    其次再引入Spring Cloud相关组件依赖

    <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
    </dependencyManagement>

    其它组件依赖引入(修正:如果引入了nacos-api相关的JSON依赖,那么fastjson就不需要再引入了,否则可能冲突):

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>${fastjson.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    注意,这里有个坑,spring cloud gateway使用的web框架为webflux,和springMVC不兼容。所以不要引入(修正:只有gateway服务不用引入springMVC,其他需要引入)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    2)三个子模块:gateway、nacos-consumer、nacos-provider

    <modules>
      <module>nacos-provider</module>
      <module>nacos-consumer</module>
      <module>gateway</module>
    </modules>

    结构截图如下所示:

    3)三个服务的端口分别为:

      nacos-consume:6001

      nacos-provider:6002

      gateway:6003

    4)服务架构如下:

                        

    2、编写测试代码

    (1)在gateway模块中主要实现以下功能:

    第一,从Nacos配置中心中加载动态路由的相关配置,就需要读取Nacos的命名空间namespace,通过dataId获取配置

    /**
     * 路由类配置
     */
    @Configuration
    public class GatewayConfig {
        public static final long DEFAULT_TIMEOUT = 30000;
    
        public static String NACOS_SERVER_ADDR;
    
        public static String NACOS_NAMESPACE;
    
        public static String NACOS_ROUTE_DATA_ID;
    
        public static String NACOS_ROUTE_GROUP;
    
        @Value("${spring.cloud.nacos.discovery.server-addr}")
        public void setNacosServerAddr(String nacosServerAddr){
            NACOS_SERVER_ADDR = nacosServerAddr;
        }
    
        @Value("${spring.cloud.nacos.discovery.namespace}")
        public void setNacosNamespace(String nacosNamespace){
            NACOS_NAMESPACE = nacosNamespace;
        }
    
        @Value("${nacos.gateway.route.config.data-id}")
        public void setNacosRouteDataId(String nacosRouteDataId){
            NACOS_ROUTE_DATA_ID = nacosRouteDataId;
        }
    
        @Value("${nacos.gateway.route.config.group}")
        public void setNacosRouteGroup(String nacosRouteGroup){
            NACOS_ROUTE_GROUP = nacosRouteGroup;
        }
    
    }

    properties配置关于Nacos下读取gateway-router的配置:

    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    spring.cloud.nacos.discovery.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea
    nacos.gateway.route.config.data-id=gateway-router
    nacos.gateway.route.config.group=DEFAULT_GROUP

    第二,初始化路由,监听动态路由配置的数据源变化;

    /**
     *
     * 通过nacos下发动态路由配置,监听Nacos中gateway-route配置
     *
     */
    @Component
    @Slf4j
    @DependsOn({"gatewayConfig"}) // 依赖于gatewayConfig bean
    public class DynamicRouteServiceImplByNacos {
    
        @Autowired
        private DynamicRouteServiceImpl dynamicRouteService;
    
    
        private ConfigService configService;
    
        @PostConstruct
        public void init() {
            log.info("gateway route init...");
            try{
                configService = initConfigService();
                if(configService == null){
                    log.warn("initConfigService fail");
                    return;
                }
                String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
                log.info("获取网关当前配置:
    {}",configInfo);
                List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                for(RouteDefinition definition : definitionList){
                    log.info("update route : {}",definition.toString());
                    dynamicRouteService.add(definition);
                }
            } catch (Exception e) {
                log.error("初始化网关路由时发生错误",e);
            }
            dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
        }
    
        /**
         * 监听Nacos下发的动态路由配置
         * @param dataId
         * @param group
         */
        public void dynamicRouteByNacosListener (String dataId, String group){
            try {
                configService.addListener(dataId, group, new Listener()  {
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        log.info("进行网关更新:
    
    {}",configInfo);
                        List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                        for(RouteDefinition definition : definitionList){
                            log.info("update route : {}",definition.toString());
                            dynamicRouteService.update(definition);
                        }
                    }
                    @Override
                    public Executor getExecutor() {
                        log.info("getExecutor
    
    ");
                        return null;
                    }
                });
            } catch (NacosException e) {
                log.error("从nacos接收动态路由配置出错!!!",e);
            }
        }
    
        /**
         * 初始化网关路由 nacos config
         * @return
         */
        private ConfigService initConfigService(){
            try{
                Properties properties = new Properties();
                properties.setProperty("serverAddr",GatewayConfig.NACOS_SERVER_ADDR);
                properties.setProperty("namespace",GatewayConfig.NACOS_NAMESPACE);
                return configService= NacosFactory.createConfigService(properties);
            } catch (Exception e) {
                log.error("初始化网关路由时发生错误",e);
                return null;
            }
        }
    }

    第三,刷新最新的动态路由变化,实现动态增删改路由

    /**
     * 动态更新路由网关service
     * 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
     * 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
     */
    @Slf4j
    @Service
    public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
        @Autowired
        private RouteDefinitionWriter routeDefinitionWriter;
    
        /**
         * 发布事件
         */
        @Autowired
        private ApplicationEventPublisher publisher;
    
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
            this.publisher = applicationEventPublisher;
        }
    
        /**
         * 删除路由
         * @param id
         * @return
         */
        public String delete(String id) {
            try {
                log.info("gateway delete route id {}",id);
                this.routeDefinitionWriter.delete(Mono.just(id));
                return "delete success";
            } catch (Exception e) {
                return "delete fail";
            }
        }
        /**
         * 更新路由
         * @param definition
         * @return
         */
        public String update(RouteDefinition definition) {
            try {
                log.info("gateway update route {}",definition);
                this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
            } catch (Exception e) {
                return "update fail,not find route  routeId: "+definition.getId();
            }
            try {
                routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                this.publisher.publishEvent(new RefreshRoutesEvent(this));
                return "success";
            } catch (Exception e) {
                return "update route fail";
            }
        }
    
        /**
         * 增加路由
         * @param definition
         * @return
         */
        public String add(RouteDefinition definition) {
            log.info("gateway add route {}",definition);
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        }
    }

    (2)在consumer创建ConsumeController:通过访问gateway网关/consume/sayHello/{name}("pattern": "/consume/**"),跳转至nacos-consumer服务("uri": "lb://nacos-consumer"),

    @RequestMapping("/consume/")
    @Slf4j
    public class ConsumeController {
    
        @GetMapping("/sayHello/{name}")
        public String sayHello(@PathVariable("name") String name){
            log.info("I'm calling nacos-consumer service by dynamic gateway...");
            return name + " Hi~, I'm from nacos-consumer";
        }
    }

    (3)在provider创建ProviderController:通过访问gateway网关/provide/sayHello/{name}("pattern": "/provide/**"),跳转至nacos-provider服务("uri": "lb://nacos-provider")

    @RestController
    @RequestMapping("/provide/")
    @Slf4j
    public class ProviderController {
    
        @GetMapping("/sayHello/{name}")
        public String sayHello(@PathVariable("name") String name){
            log.info("I'm calling nacos-provider service by dynamic gateway...");
            return name + " Hi~, I'm from nacos-provider";
        }
    }

    三、测试动态网关配置

    1、启动服务,观察注册中心

    分别启动gateway、nacos-consumer、nacos-provider三个服务,观察是否已经在Nacos上正确注册

    注意:需要指定注册中心的namespace为dev的空间,即spring.cloud.nacos.discovery.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea

    2、访问网关,观察服务日志

    (1)查看gateway服务的初始化启动日志:会发现可以正常从Nacos获取配置gateway-router网关配置文件内容,并进行正确路由加载...

    2020-05-10 14:33:44.557  INFO 1272 --- [           main] c.g.r.DynamicRouteServiceImplByNacos     : gateway route init...
    2020-05-10 14:33:44.578  INFO 1272 --- [           main] c.g.r.DynamicRouteServiceImplByNacos     : 获取网关当前配置:
    [{
        "id": "consumer-router",
        "order": 0,
        "predicates": [{
            "args": {
                "pattern": "/consume/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-consumer"
    },{
        "id": "provider-router",
        "order": 2,
        "predicates": [{
            "args": {
                "pattern": "/provide/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-provider"
    }]
    2020-05-10 14:33:44.691  INFO 1272 --- [           main] c.g.r.DynamicRouteServiceImplByNacos     : update route : RouteDefinition{id='consumer-router', predicates=[PredicateDefinition{name='Path', args={pattern=/consume/**}}], filters=[], uri=lb://nacos-consumer, order=0, metadata={}}
    2020-05-10 14:33:44.691  INFO 1272 --- [           main] c.g.service.DynamicRouteServiceImpl      : gateway add route RouteDefinition{id='consumer-router', predicates=[PredicateDefinition{name='Path', args={pattern=/consume/**}}], filters=[], uri=lb://nacos-consumer, order=0, metadata={}}
    2020-05-10 14:33:45.192  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [After]
    2020-05-10 14:33:45.192  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Before]
    2020-05-10 14:33:45.192  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Between]
    2020-05-10 14:33:45.193  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Cookie]
    2020-05-10 14:33:45.193  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Header]
    2020-05-10 14:33:45.193  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Host]
    2020-05-10 14:33:45.194  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Method]
    2020-05-10 14:33:45.194  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Path]
    2020-05-10 14:33:45.194  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Query]
    2020-05-10 14:33:45.194  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [ReadBodyPredicateFactory]
    2020-05-10 14:33:45.194  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [RemoteAddr]
    2020-05-10 14:33:45.194  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Weight]
    2020-05-10 14:33:45.194  INFO 1272 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [CloudFoundryRouteService]
    2020-05-10 14:33:45.335  INFO 1272 --- [           main] c.g.r.DynamicRouteServiceImplByNacos     : update route : RouteDefinition{id='provider-router', predicates=[PredicateDefinition{name='Path', args={pattern=/provide/**}}], filters=[], uri=lb://nacos-provider, order=2, metadata={}}
    2020-05-10 14:33:45.335  INFO 1272 --- [           main] c.g.service.DynamicRouteServiceImpl      : gateway add route RouteDefinition{id='provider-router', predicates=[PredicateDefinition{name='Path', args={pattern=/provide/**}}], filters=[], uri=lb://nacos-provider, order=2, metadata={}}
    2020-05-10 14:33:45.336  INFO 1272 --- [           main] c.g.r.DynamicRouteServiceImplByNacos     : update route : RouteDefinition{id='github-router', predicates=[PredicateDefinition{name='Path', args={pattern=/github}}], filters=[], uri=https://github.com, order=3, metadata={}}
    2020-05-10 14:33:45.336  INFO 1272 --- [           main] c.g.service.DynamicRouteServiceImpl      : gateway add route RouteDefinition{id='github-router', predicates=[PredicateDefinition{name='Path', args={pattern=/github}}], filters=[], uri=https://github.com, order=3, metadata={}}
    View Code

    但这只能说明是初始化静态路由,下面我们改变gateway-router网关配置内容,追加github-router路由

    [{
        "id": "consumer-router",
        "order": 0,
        "predicates": [{
            "args": {
                "pattern": "/consume/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-consumer"
    },{
        "id": "provider-router",
        "order": 2,
        "predicates": [{
            "args": {
                "pattern": "/provide/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-provider"
    },{
        "id": "github-router",
        "order": 2,
        "predicates": [{
            "args": {
                "pattern": "/github/**"
            },
            "name": "Path"
        }],
        "uri": "https://github.com"
    }]

    之后点击发布更新路由配置

    观察gateway服务日志,有没有监听,并且进行正确的路由更新:如下日志所示,最新路由配置立马被打印,并且进行正确路由更新

    2020-05-10 14:42:27.576  INFO 1272 --- [d5-b0a8fbeed8ea] c.g.r.DynamicRouteServiceImplByNacos     : 进行网关更新:
    
    [{
        "id": "consumer-router",
        "order": 0,
        "predicates": [{
            "args": {
                "pattern": "/consume/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-consumer"
    },{
        "id": "provider-router",
        "order": 2,
        "predicates": [{
            "args": {
                "pattern": "/provide/**"
            },
            "name": "Path"
        }],
        "uri": "lb://nacos-provider"
    },{
        "id": "github-router",
        "order": 2,
        "predicates": [{
            "args": {
                "pattern": "/github/**"
            },
            "name": "Path"
        }],
        "uri": "https://github.com"
    }]
    2020-05-10 14:42:27.576  INFO 1272 --- [d5-b0a8fbeed8ea] c.g.r.DynamicRouteServiceImplByNacos     : update route : RouteDefinition{id='consumer-router', predicates=[PredicateDefinition{name='Path', args={pattern=/consume/**}}], filters=[], uri=lb://nacos-consumer, order=0, metadata={}}
    2020-05-10 14:42:27.576  INFO 1272 --- [d5-b0a8fbeed8ea] c.g.service.DynamicRouteServiceImpl      : gateway update route RouteDefinition{id='consumer-router', predicates=[PredicateDefinition{name='Path', args={pattern=/consume/**}}], filters=[], uri=lb://nacos-consumer, order=0, metadata={}}
    2020-05-10 14:42:27.578  INFO 1272 --- [d5-b0a8fbeed8ea] c.g.r.DynamicRouteServiceImplByNacos     : update route : RouteDefinition{id='provider-router', predicates=[PredicateDefinition{name='Path', args={pattern=/provide/**}}], filters=[], uri=lb://nacos-provider, order=2, metadata={}}
    2020-05-10 14:42:27.578  INFO 1272 --- [d5-b0a8fbeed8ea] c.g.service.DynamicRouteServiceImpl      : gateway update route RouteDefinition{id='provider-router', predicates=[PredicateDefinition{name='Path', args={pattern=/provide/**}}], filters=[], uri=lb://nacos-provider, order=2, metadata={}}
    2020-05-10 14:42:27.580  INFO 1272 --- [d5-b0a8fbeed8ea] c.g.r.DynamicRouteServiceImplByNacos     : update route : RouteDefinition{id='github-router', predicates=[PredicateDefinition{name='Path', args={pattern=/github/**}}], filters=[], uri=https://github.com, order=2, metadata={}}
    2020-05-10 14:42:27.580  INFO 1272 --- [d5-b0a8fbeed8ea] c.g.service.DynamicRouteServiceImpl      : gateway update route RouteDefinition{id='github-router', predicates=[PredicateDefinition{name='Path', args={pattern=/github/**}}], filters=[], uri=https://github.com, order=2, metadata={}}
    View Code

    其实,还有办法可以知道我们的gateway服务有没有监听Nacos的gateway-router配置,那就是在Nacos Console--->监听查询----->选择配置---->输入配置文件的namespace与Group: 可以发现我本地IP地址127.0.0.1对配置文件gateway-router进行了监听

    (2)访问gateway网关服务:http://localhost:6003/consume/sayHello/nacos

      

    查看consumer服务日志:

    2020-05-10 14:55:07.257  INFO 6552 --- [nio-6001-exec-2] c.n.c.controller.ConsumeController       : I'm calling nacos-consumer service by dynamic gateway...

    发现跳转至consumer服务,并且访问了consumer服务的CosnumerController

    (3)访问gateway网关服务:http://localhost:6003/provider/sayHello/nacos

      

    查看provider服务日志:

    2020-05-10 14:56:56.144  INFO 10024 --- [nio-6002-exec-1] c.n.p.controller.ProviderController      : I'm calling nacos-provider service by dynamic gateway...

    发现跳转至consumer服务,并且访问了provider服务的ProviderController

    (4)访问访问gateway网关服务:http://localhost:6003/github,正确跳转至github页面

    四、总结

      1)Spring Cloud Gateway作用不光只是简单的跳转重定向,还可以实现用户的验证登录,解决跨域,日志拦截,权限控制,限流,熔断,负载均衡,黑名单和白名单机制等。是微服务架构不二的选择

      2)Nacos的配置中心支持动态获取配置文件,可以将一些全局的经常变更的配置文件放在Nacos下,需要到微服务自行获取。

  • 相关阅读:
    保存时出错jup
    Spyder默认页面布局调整
    根据所处位置提取单元格内容的函数(left、right、mid)和查找字符串位于单元格内容第几位的函数(find)
    excel打印出现多余空白页
    Excel的布尔值运算
    excel VBA一个fuction同时执行多个正则表达式,实现方法
    excel VBA把一个单元格内容按逗号拆分并依次替换到另一个单元格的括号里面(本题例子,把文本中的括号换成{答案}的格式,并按顺序填空)
    excel自动记录项目完成进度,是否逾期,逾期/提前完成天数,计算天数可以把now()改为today()
    jquery循环动画
    jquery动画(控制动画隐藏、显示时间轴)
  • 原文地址:https://www.cnblogs.com/jian0110/p/12862569.html
Copyright © 2011-2022 走看看