前言:
在微服务的解决方案中,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来完成自定义动态路由数据源对象的配置:
@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的动态配置监听。
重写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控制台更改路由配置信息,将会近乎实时的刷新微服务路由规则,这是很方便的一种管理多个微服务路由的方式。
另外的服务访问方式如下:
================================= 分割线 =====================================