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>
其它组件依赖引入:
<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不兼容。所以不要引入
<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("获取网关当前配置:\r\n{}",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("进行网关更新:\n\r{}",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\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; } } }
第三,刷新最新的动态路由变化,实现动态增删改路由
/** * 动态更新路由网关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={}}
但这只能说明是初始化静态路由,下面我们改变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={}}
其实,还有办法可以知道我们的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下,需要到微服务自行获取。
推荐阅读
-
Spring Cloud 动态刷新配置信息教程详解
-
Spring Cloud 动态刷新配置信息教程详解
-
Spring Cloud GateWay 路由转发规则介绍详解
-
Spring Cloud动态配置实现原理与源码分析
-
Spring Cloud动态配置实现原理与源码分析
-
Spring Cloud Gateway的动态路由怎样做?集成Nacos实现很简单
-
详解Spring Cloud Gateway基于服务发现的默认路由规则
-
vue动态路由配置及路由传参的方式
-
基于Nacos实现Spring Cloud Gateway实现动态路由的方法
-
Spring Cloud Getway 动态配置路由及自定义修改请求内容、请求url、相应内容