Nacos+Spring|Nacos+Spring Cloud Gateway动态路由配置实现步骤
目录
- 前言
- 一、Nacos环境准备
- 1、启动Nacos配置中心并创建路由配置
- 2、连接Nacos配置中心
- 二、项目构建
- 1、项目结构
- 2、编写测试代码
- 三、测试动态网关配置
- 1、启动服务,观察注册中心
- 2、访问网关,观察服务日志
- 四、总结
前言 Nacos最近项目一直在使用,其简单灵活,支持更细粒度的命令空间,分组等为麻烦复杂的环境切换提供了方便;同时也很好支持动态路由的配置,只需要简单的几步即可。在国产的注册中心、配置中心中比较突出,容易上手,本文通过gateway、nacos-consumer、nacos-provider三个简单模块来展示:Nacos下动态路由配置。
博文中源码已上传至github(https://github.com/Jian0110/learning-cloudalibaba),欢迎小伙伴们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、项目结构
创建简单的springboot多模块结构,推荐使用idea创建
1)Nacos父模块:
com.springcloud nacos0.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 importorg.springframework.cloud spring-cloud-dependencies${spring-cloud.version} pom import
其它组件依赖引入(修正:如果引入了nacos-api相关的JSON依赖,那么fastjson就不需要再引入了,否则可能冲突):
com.alibaba fastjson${fastjson.version} org.projectlombok lombok${lombok.version} providedorg.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+Spring|Nacos+Spring Cloud Gateway动态路由配置实现步骤】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 {@Autowiredprivate DynamicRouteServiceImpl dynamicRouteService; private ConfigService configService; @PostConstructpublic 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); ListdefinitionList = 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(){@Overridepublic 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); }@Overridepublic 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 {@Autowiredprivate RouteDefinitionWriter routeDefinitionWriter; @Autowiredprivate RouteDefinitionLocator routeDefinitionLocator; /*** 发布事件*/@Autowiredprivate ApplicationEventPublisher publisher; @Overridepublic 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(Listdefinitions) {log.info("gateway update route {}",definitions); // 删除缓存routerDefinitionList 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 routerouteId: "+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"; }}
三、测试动态网关配置
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.557INFO 1272 --- [main] c.g.r.DynamicRouteServiceImplByNacos: gateway route init...2020-05-10 14:33:44.578INFO 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.691INFO 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=https://www.it610.com/article/{}}2020-05-10 14:33:44.691INFO 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=https://www.it610.com/article/{}}2020-05-10 14:33:45.192INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [After]2020-05-10 14:33:45.192INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Before]2020-05-10 14:33:45.192INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Between]2020-05-10 14:33:45.193INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Cookie]2020-05-10 14:33:45.193INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Header]2020-05-10 14:33:45.193INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Host]2020-05-10 14:33:45.194INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Method]2020-05-10 14:33:45.194INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Path]2020-05-10 14:33:45.194INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Query]2020-05-10 14:33:45.194INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [ReadBodyPredicateFactory]2020-05-10 14:33:45.194INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [RemoteAddr]2020-05-10 14:33:45.194INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [Weight]2020-05-10 14:33:45.194INFO 1272 --- [main] o.s.c.g.r.RouteDefinitionRouteLocator: Loaded RoutePredicateFactory [CloudFoundryRouteService]2020-05-10 14:33:45.335INFO 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=https://www.it610.com/article/{}}2020-05-10 14:33:45.335INFO 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=https://www.it610.com/article/{}}2020-05-10 14:33:45.336INFO 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=https://www.it610.com/article/{}}2020-05-10 14:33:45.336INFO 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=https://www.it610.com/article/{}}
但这只能说明是初始化静态路由,下面我们改变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.576INFO 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.576INFO 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=https://www.it610.com/article/{}}2020-05-10 14:42:27.576INFO 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=https://www.it610.com/article/{}}2020-05-10 14:42:27.578INFO 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=https://www.it610.com/article/{}}2020-05-10 14:42:27.578INFO 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=https://www.it610.com/article/{}}2020-05-10 14:42:27.580INFO 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=https://www.it610.com/article/{}}2020-05-10 14:42:27.580INFO 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=https://www.it610.com/article/{}}
其实,还有办法可以知道我们的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下,需要到微服务自行获取。
2020.12.28解决删除路由不生效问题,主要是利用RouteDefinitionLocator先读取变化之前的RouteDefinition,之后删除重新再更新,或者可以实现routeDefinitionWriter、RouteDefinitionLocator重写如下方法:
Monosave(Mono route); Mono delete(Mono routeId); Flux getRouteDefinitions();
到此这篇关于Nacos+Spring Cloud Gateway动态路由配置实现步骤的文章就介绍到这了,更多相关Spring Cloud Gateway动态路由内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- Spring|Spring Cloud Feign实现文件上传下载的示例代码
- 为Google|为Google Cloud配置深度学习环境(CUDA、cuDNN、Tensorflow2、VScode远程ssh等)
- 【SpringCloud-Alibaba系列教程】8.一文学会使用sentinel
- java|java B2B2C 仿淘宝电子商城系统-Spring Cloud构建分布式电子商务平台
- Spring|Spring Cloud Feign组件
- Spring|Spring Cloud Alibaba之负载均衡组件 - Ribbon
- Spring|Spring Cloud学习day98(Eureka的原理和Ribbon负载均衡)
- 史上最简单的SpringCloud教程|史上最简单的SpringCloud教程 | 第十篇: 高可用的服务注册中心(Finchley版本)
- Spring|Spring Cloud Gateway真的有那么差吗()
- (三十)java版spring|(三十)java版spring cloud+spring boot+redis多租户社交电子商务平台- gateway限流