博客
关于我
Nacos+Spring Cloud Gateway动态路由配置
阅读量:575 次
发布时间:2019-03-11

本文共 20190 字,大约阅读时间需要 67 分钟。

前言

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

 博文中源码已上传至github(),欢迎小伙伴们star

  


一、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@EnableDiscoveryClientpublic class GatewayApplication{    public static void main( String[] args )    {        SpringApplication.run(GatewayApplication.class, args);    }}

 

二、项目构建

1、项目结构

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

1)Nacos父模块:

com.springcloud
nacos
0.0.1-SNAPSHOT
nacos
Nacos Demo

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

  
com.alibaba.cloud
  
spring-cloud-starter-alibaba-nacos-discovery
  
${alibaba-nacos.version}
  
com.alibaba.cloud
  
spring-cloud-starter-alibaba-nacos-config
  
${alibaba-nacos.version}

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

org.springframework.boot
spring-boot-dependencies
${spring-boot.version}
pom
import
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import

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

com.alibaba
fastjson
${fastjson.version}
org.projectlombok
lombok
${lombok.version}
provided
org.slf4j
slf4j-api
${slf4j.version}
org.springframework.boot
spring-boot-starter-actuator

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

org.springframework.boot
spring-boot-starter-web

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

  
nacos-provider
  
nacos-consumer
  
gateway

结构截图如下所示:

 

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

  nacos-consume:6001

  nacos-provider:6002

  gateway:6003

4)服务架构如下:

 

                    

 

2、编写测试代码

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

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

/** * 路由类配置 */@Configurationpublic 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:8848spring.cloud.nacos.discovery.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8eanacos.gateway.route.config.data-id=gateway-routernacos.gateway.route.config.group=DEFAULT_GROUP

第二,初始化路由,监听动态路由配置的数据源变化(2020.12.28 解决删除路由不生效问题);

/** * * 通过nacos下发动态路由配置,监听Nacos中gateway-route配置 * */@Component@Slf4j@DependsOn({"gatewayConfig"}) // 依赖于gatewayConfig beanpublic 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("获取网关当前配置:\r\n{}",configInfo);            List
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("进行网关更新:\n\r{}",configInfo); List
definitionList = JSON.parseArray(configInfo, RouteDefinition.class); log.info("update route : {}",definitionList.toString()); dynamicRouteService.updateList(definitionList); } @Override public Executor getExecutor() { log.info("getExecutor\n\r"); 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; } }}

第三,刷新最新的动态路由变化,实现动态增删改路由(2020.12.28 解决删除路由不生效问题)

/** * 动态更新路由网关service * 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware * 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。 */@Slf4j@Servicepublic class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {    @Autowired    private RouteDefinitionWriter routeDefinitionWriter;    @Autowired    private RouteDefinitionLocator routeDefinitionLocator;    /**     * 发布事件     */    @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)).subscribe();            this.publisher.publishEvent(new RefreshRoutesEvent(this));            return "delete success";        } catch (Exception e) {            return "delete fail";        }    }    /**     * 更新路由     * @param definitions     * @return     */    public String updateList(List
definitions) { log.info("gateway update route {}",definitions); // 删除缓存routerDefinition List
routeDefinitionsExits = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst(); if (!CollectionUtils.isEmpty(routeDefinitionsExits)) { routeDefinitionsExits.forEach(routeDefinition -> { log.info("delete routeDefinition:{}", routeDefinition); delete(routeDefinition.getId()); }); } definitions.forEach(definition -> { updateById(definition); }); return "success"; } /** * 更新路由 * @param definition * @return */ public String updateById(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/")@Slf4jpublic 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/")@Slf4jpublic 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网关服务:

  

 

查看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网关服务:

  

查看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网关服务:github,正确跳转至github页面

四、总结

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

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

 

            ---------------------------------Change  log--------------------------------

2020.12.28 解决删除路由不生效问题,主要是利用RouteDefinitionLocator先读取变化之前的RouteDefinition,之后删除重新再更新,或者可以实现routeDefinitionWriter、RouteDefinitionLocator重写如下方法:

Mono
save(Mono
route);Mono
delete(Mono
routeId);Flux
getRouteDefinitions();

 

转载地址:http://nhsvz.baihongyu.com/

你可能感兴趣的文章
MQ 重复消费如何解决?
查看>>
mqtt broker服务端
查看>>
MQTT 保留消息
查看>>
MQTT 持久会话与 Clean Session 详解
查看>>
MQTT工作笔记0007---剩余长度
查看>>
MQTT工作笔记0009---订阅主题和订阅确认
查看>>
Mqtt搭建代理服务器进行通信-浅析
查看>>
MS Edge浏览器“STATUS_INVALID_IMAGE_HASH“兼容性问题
查看>>
ms sql server 2008 sp2更新异常
查看>>
MS UC 2013-0-Prepare Tool
查看>>
MSBuild 教程(2)
查看>>
msbuild发布web应用程序
查看>>
MSB与LSB
查看>>
MSCRM调用外部JS文件
查看>>
MSCRM调用外部JS文件
查看>>
MSEdgeDriver (Chromium) 不适用于版本 >= 79.0.313 (Canary)
查看>>
MsEdgeTTS开源项目使用教程
查看>>
msf
查看>>
MSSQL数据库查询优化(一)
查看>>
MSSQL数据库迁移到Oracle(二)
查看>>