前言:
在微服务的解决方案中,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操作,但一般不需要在代码中更改路由信息,留空即可。
总结四步:
- 路由配置文件定义
- NacosConfigListenr监听配置变化
- 发送使Gateway路由刷新Event
- 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的动态配置监听。
重写RouteDefinitionLocator
的getRouteDefinitions()
方法,获取路由信息列表将从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控制台更改路由配置信息,将会近乎实时的刷新微服务路由规则,这是很方便的一种管理多个微服务路由的方式。
另外的服务访问方式如下: