SpringCloud Gateway结合Nacos实现微服务动态路由的例子,以及相关问题解决(含代码)

2022-07-20 10:48:42

前言:

在微服务的解决方案中,Nacos可以实现注册中心,服务发现,配置中心,负载均衡(结合ribbon/openfign)等一系列服务治理的功能,其内置管理页面,使用起来方便灵活且高效。
它和SpringCloud的融合参考nacos.io文档:Nacos SpringCloud 快速开始
在往常的Gateway使用中,微服务的路由变更往往需要重启,才能再次载入新的路由关系映射,
SpringCloudGateway作为高性能的微服务网关,其提供了很多FilterFactory供我们做相关扩展,而路由的crud也提供了相关的扩展API:RouteDefinitionRepository,自然也可以很顺畅的与Nacos的配置中心功能相结合,来达到动态路由的效果。

详情见代码供大家参考:代码样例

一、实现思路

  • 在nacos的配置中心建立一个json格式的文件来定义route信息,基于nacos来管理该配置。
  • 利用nacos的配置监听功能监听route.json的变化,在该配置发生变化时,发送一个可以刷新Gateway路由的event:RefreshRoutesEvent
  • Gateway的RouteLocator(实例是:CachingRouteLocator) 监听RefreshRoutesEvent事件,用以刷新路由。
  • 自行实现的RouteDefinitionRepository的对象重写了getRouteDefinitions()方法,Gateway在刷新路由的步骤中,将会调用该方法获取路由信息,另外该对象实现的接口:RouteDefinitionWriter提供了save和delete操作,但一般不需要在代码中更改路由信息,留空即可。

总结四步:

  1. 路由配置文件定义
  2. NacosConfigListenr监听配置变化
  3. 发送使Gateway路由刷新Event
  4. Gateway刷新路由时获取路由配置文件

二、代码解析

1.NacosDynamicRoute来完成自定义动态路由数据源对象的配置:

@Configurationclass NacosDynamicRoute{@BeanfunnacosRouteDefinitionRepository(
        publisher: ApplicationEventPublisher,
        nacosConfigManager: NacosConfigManager,@Value("\${spring.cloud.nacos.config.router-data-id:gateway-router.json}")
        routerDataId: String)=NacosRouteDefinitionRepository(routerDataId, publisher, nacosConfigManager)}

routeDataId参数支持在配置文件中自定义,如无自定义则采用:“gateway-router.json”,这就要求在Nacos中建立配置时使用该DataId,
自定义的nacosRouteDefinitionRepository对象则代替了Gateway默认的inMemoryRouteDefinitionRepository对象,如下GatewayAutoConfiguration自动配置类使用了@ConditionalOnMissingBean,表示了如果我们没有提供数据源对象,则将会使用inMemoryRouteDefinitionRepository
在这里插入图片描述

2.NacosRouteDefinitionRepository实现动态路由信息数据源、Nacos的配置监听并发送RefreshRoutesEvent

classNacosRouteDefinitionRepository(privateval routerDataId: String,privateval publisher: ApplicationEventPublisher,privateval nacosConfigManager: NacosConfigManager): RouteDefinitionRepository{privateval log= LoggerFactory.getLogger(javaClass.name)privateval getConfigTimeoutMs=6000Linit{addListener()}/**
     * 获取路由列表信息
     *
     * @return
     */privatefunrouteDefinitions0(): List<RouteDefinition>{try{val content= nacosConfigManager.configService.getConfig(
                routerDataId,
                nacosConfigManager.nacosConfigProperties.group,
                getConfigTimeoutMs)returnparseRouteDefinition(content)}catch(e: NacosException){
            log.error("nacos gateway路由文件解析失败", e)}returnlistOf()}overridefungetRouteDefinitions()= Flux.fromIterable(routeDefinitions0())/**
     * 添加Nacos监听
     */privatefunaddListener(){try{
            nacosConfigManager.configService.addListener(
                routerDataId,
                nacosConfigManager.nacosConfigProperties.group,object: Listener{overridefungetExecutor()=nulloverridefunreceiveConfigInfo(configInfo: String){
                        publisher.publishEvent(RefreshRoutesEvent(this))}})}catch(e: NacosException){
            log.error("nacos gateway添加路由变更监听器失败", e)}}overridefunsave(route: Mono<RouteDefinition>)=nulloverridefundelete(routeId: Mono<String>)=null/**
     * 解析路由
     *
     * @param content
     * @return
     */privatefunparseRouteDefinition(content: String): List<RouteDefinition>=
        JSONObject.parseArray(content, RouteDefinition::class.java)}

对象的init(初始化)方法中调用了addListener(),添加对Nacos的动态配置监听。
重写RouteDefinitionLocatorgetRouteDefinitions()方法,获取路由信息列表将从Nacos的配置中心,通过DataId、GroupId(namespace隐式的从gateway服务的配置获取)来定位配置文件,并得到其内容以String的形式,之后通过JSON解析为List<RouteDefinition>,供外部调用。

三、可能的问题解决

1.在实际完成配置后,访问对应的接口可能会出现503,一般出现的503的原因有两个:

1).缺少实际的负载均衡配置,解决方式是增加ribbon/openfeign的依赖,202x的SpringCloud版本需要加入loadbalancer,而低版本的则需要加入openfeign:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>

这些依赖的配置跟使用的SpringCloud版本,Nacos的版本有关,具体细节不在赘述。
另外相关版本需要严格按照官方建议的进行使用,具体的版本对应关系查看:spring-cloud-alibaba版本说明

2).被访问的微服务在nacos的namespace,group与Gateway所在的不一致,导致503,解决方式就是Gateway和相关微服务使用同一namespace和group

四、Nacos上的配置

1.Nacos控制台新建配置文件,填入Data Id为gateway-router.json(根据实际情况可自定义名称,@Value routerDataId获取到即可)
在这里插入图片描述

gateway-router.json内容:

[{"id":"server1-application","predicates":[{"name":"Path","args":{"pattern":"/server1-application/**"}}],"uri":"lb://server1-application","filters":[{"args":{"parts": 1},"name":"StripPrefix"}]},{"id":"server2-application","predicates":[{"name":"Path","args":{"pattern":"/server2-application/**"}}],"uri":"lb://server2-application","filters":[{"args":{"parts": 1},"name":"StripPrefix"}]}]

配置方式自行参考官网:the path route-predicate-factory,这里我只用到了Path匹配,并截掉实际访问url第一个/之前的路径。

五、总结

通过少量代码,配置,即可完成SpringCloudGateway的动态路由,其他注册中心也是同理,其他动态配置也是同理,思想都是一样的,本地通过中间件提供的监听API对配置进行监听,有变更时将获取相关配置,拿到配置就可以做相关的更新。
当然文中的方式则是通过Gateway提供的事件:RefreshRoutesEvent来完成的。假设没有这个事件,我们可能需要别的方式,比如监听到路由配置文件变化后,从Listener匿名对象的参数回调可得到配置文件内容,通过JSON或者别的解析方式,转为对应的路由cache类型(比如map),直接赋值给路由并refresh。(没有开放API时可能通过反射暴力修改其中的路由cache)
Nacos结合SpringCloud的方式参考官网:nacos.io
Gateway的动态路由在以上代码及配置正确的情况下,在Nacos控制台更改路由配置信息,将会近乎实时的刷新微服务路由规则,这是很方便的一种管理多个微服务路由的方式。
另外的服务访问方式如下:
在这里插入图片描述

================================= 分割线 =====================================

最后附上实际的代码参考

springcloud-nacos-example


  • 作者:kam1996
  • 原文链接:https://blog.csdn.net/zx156955/article/details/123326298
    更新时间:2022-07-20 10:48:42