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

前言:

在微服务的解决方案中,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来完成自定义动态路由数据源对象的配置:

@Configuration
class NacosDynamicRoute {

    @Bean
    fun nacosRouteDefinitionRepository(
        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

class NacosRouteDefinitionRepository(
    private val routerDataId: String,
    private val publisher: ApplicationEventPublisher,
    private val nacosConfigManager: NacosConfigManager
) : RouteDefinitionRepository {

    private val log = LoggerFactory.getLogger(javaClass.name)

    private val getConfigTimeoutMs = 6000L

    init {
        addListener()
    }

    /**
     * 获取路由列表信息
     *
     * @return
     */
    private fun routeDefinitions0(): List<RouteDefinition> {
        try {
            val content = nacosConfigManager.configService.getConfig(
                routerDataId,
                nacosConfigManager.nacosConfigProperties.group,
                getConfigTimeoutMs
            )
            return parseRouteDefinition(content)
        } catch (e: NacosException) {
            log.error("nacos gateway路由文件解析失败", e)
        }
        return listOf()
    }

    override fun getRouteDefinitions() = Flux.fromIterable(routeDefinitions0())

    /**
     * 添加Nacos监听
     */
    private fun addListener() {
        try {
            nacosConfigManager.configService.addListener(
                routerDataId,
                nacosConfigManager.nacosConfigProperties.group,
                object : Listener {
                    override fun getExecutor() = null

                    override fun receiveConfigInfo(configInfo: String) {
                        publisher.publishEvent(RefreshRoutesEvent(this))
                    }
                })
        } catch (e: NacosException) {
            log.error("nacos gateway添加路由变更监听器失败", e)
        }
    }

    override fun save(route: Mono<RouteDefinition>) = null

    override fun delete(routeId: Mono<String>) = null

    /**
     * 解析路由
     *
     * @param content
     * @return
     */
    private fun parseRouteDefinition(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


有什么问题或者看法,都欢迎大家留言区讨论~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值