前言
在微服务架构中,API 网关是各个服务之间的入口点,承担着路由、负载均衡、安全认证等重要功能。为了实现动态的路由配置管理,通常需要通过中心化的配置管理系统来实现灵活的路由更新,而无需重启网关服务。Nacos 作为一个开源的动态服务发现与配置管理平台,可以方便地实现这一目标。本文将介绍如何利用 Nacos 配置中心来动态更新 Spring Cloud Gateway 的路由配置,确保路由信息的实时更新,并提升系统的可维护性和灵活性。
动态路由
监听Nacos配置变更
要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息。
有两件事需要做:
- 监听Nacos配置变更的消息
- 当配置变更时,将最新的路由信息更新到网关路由表
在Nacos官网中给出了手动监听Nacos配置变更的SDK:(详情在文档里面)
Nacos文档
如果希望 Nacos 推送配置变更,可以使用 Nacos 动态监听配置接口来实现。
public void addListener(String dataId, String group, Listener listener)
参数名 | 参数类型 | 描述 |
---|---|---|
dataId | String | 配置 ID,采用类似 package.class(如com.taobao.tc.refund.log.level)的命名规则保证全局唯一性,class 部分建议是配置的业务含义。 全部字符小写。只允许英文字符和 4 种特殊字符(“.”、“:”、“-”、“_”)。不超过 256 字节。 |
group | String | 配置分组,建议填写产品名:模块名(如 Nacos:Test)保证唯一性。 只允许英文字符和4种特殊字符(“.”、“:”、“-”、“_”),不超过128字节。 |
listener | Listener | 监听器,配置变更进入监听器的回调函数。 |
示例代码:
String serverAddr = "{serverAddr}";
String dataId = "{dataId}";
String group = "{group}";
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
//服务器地址与nacos服务做连接
ConfigService configService = NacosFactory.createConfigService(properties);
//读取配置
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("recieve1:" + configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
// 测试让主线程不退出,因为订阅配置是守护线程,主线程退出守护线程就会退出。 正式代码中无需下面代码
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ConfigService类中
因此我们想要拿到ConfigService,只需要拿到NacosConfigManager即可。
最终代码如下:
private final NacosconfigManagernacosconfigManager;
public void initRouteConfigListener()throws NacosException {
//1.注册监听器并首次拉取配置
String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId,group,5000,new Listener(){
@0verride
public Executor getExecutor(){
return null;
}
@0verride
public void receiveConfigInfo(string configInfo){
//TOD0 监听到配置变更,更新一次配置
});
//TOD0 2.首次启动时,更新一次配置
}
在hm-gateway服务中导入依赖
对应的bootstarp文件内容如下:
spring:
application:
name: gateway
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.244.134:8848
config:
file-extension: yaml
shared-configs: #共享配置
- dataId: shared-log.yaml # 共享日志配置
更新路由
监听到路由信息后,可以利用RouteDefinitionWriter来更新路由表
/**
* @author Spencer Gibb
**/
public interface RouteDefinitionWriter{
//更新路由到路由表,如果路由id重复,则会覆盖旧的路由
Mono<Void>save(Mono<RouteDefinition> route);
// 根据路由id删除某个路由
Mono<Void>delete(Mono<String>routeId);
}
为了方便解析从Nacos读取到的路由配置,推荐使用json格式的路由配置,模板如下:
{
"id": "item",
"uri": "lb://item-service",
"predicates":[{"name": "Path",
"args":{
"_genkey_0":"/items/**",
"_gènkey_1":"/search/**"
}],
"filters": []
}
实现动态路由
spring:
cloud:
gateway:
routes:
- id: item-service
uri: lb://item-service
predicates:
- Path=/items/**,/search/**
最终创建的DynamicRouteLoader类的内容如下:
package com.hmall.gateway.routers;
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@Slf4j
@Component
@AllArgsConstructor
public class DynamicRouteLoader {
private final NacosConfigManager nacosConfigManager;
private final RouteDefinitionWriter routeDefinitionWriter;
private final String dataId = "gateway-routes.json";
private final String group = "DEFAULT_GROUP";
private final Set<String> routeIds = new HashSet<>();
@PostConstruct
public void initRouteConfigListener() throws NacosException {
//1.项目启动时,先拉取一次配置,并且添加配置监听器
String configInfo = nacosConfigManager.getConfigService()
.getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
//2.监听到配置变更,需要去更新路由表
updateConfigInfo(configInfo);
}
});
//3.第一次读取到配置,也需要更新到路由表
updateConfigInfo(configInfo);
}
public void updateConfigInfo(String configInfo){
log.debug("监听到路由配置信息:{}",configInfo);
//1.解析配置信息,转为RouteDefinition
List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
//2.删除旧的路由表
for (String routeId : routeIds) {
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
}
routeIds.clear();
//3.更新路由表
for (RouteDefinition routeDefinition : routeDefinitions) {
//3.1更新路由表
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
//3.2记录路由id,便于下一次更新的时候删除
routeIds.add(routeDefinition.getId());
}
}
}
- NacosConfigManager:获取 Nacos 配置并监听配置变化。
- RouteDefinitionWriter:用于操作 Spring Cloud Gateway 的路由配置(如添加、删除、更新路由)。
- dataId:Nacos 配置的标识,用于获取路由配置的 JSON 数据。
- group:Nacos 配置的分组。
- routeIds集合用于记录已经加载的路由 ID,方便在更新时删除旧路由。
- 使用 @PostConstruct 注解来确保类初始化后执行该方法。
- 通过 nacosConfigManager.getConfigService().getConfigAndSignListener 从Nacos 拉取初始配置,并设置一个配置监听器。
- getConfigAndSignListener:获取配置信息并注册监听器,监听配置的变化。
- 如果配置发生变化,receiveConfigInfo 方法将被调用来更新路由配置。
- 配置监听器初始化后,updateConfigInfo(configInfo) 被调用,用来处理第一次拉取到的路由配置信息。
- 解析配置信息:使用 JSONUtil.toList(configInfo, RouteDefinition.class) 将拉取到的 JSON 配置转换成 RouteDefinition 列表。
- 删除旧路由:遍历之前保存的 routeIds,使用 routeDefinitionWriter.delete 删除已经存在的路由,并清空 routeIds 集合。
- 添加新路由:遍历新的 routeDefinitions,使用 routeDefinitionWriter.save 保存新的路由配置,同时将新的路由 ID 加入 routeIds 以便下一次更新时删除旧路由。
在nacos的配置列表中添加配置gateway-routes.json
具体内容如下:
[
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
},
{
"id": "cart",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/carts/**"}
}],
"filters": [],
"uri": "lb://cart-service"
},
{
"id": "user",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
}],
"filters": [],
"uri": "lb://user-service"
},
{
"id": "trade",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/orders/**"}
}],
"filters": [],
"uri": "lb://trade-service"
},
{
"id": "pay",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/pay-orders/**"}
}],
"filters": [],
"uri": "lb://pay-service"
}
]
总结
本文通过示例代码展示了如何使用 Nacos 配置中心监听配置变更,并自动更新 Spring Cloud Gateway 的路由配置。通过这种方式,我们能够实现动态的路由更新,避免了传统的重启服务方式。利用 Nacos 作为配置中心,能够使得微服务架构中的 API 网关具有更高的灵活性和扩展性,提升系统的整体效率。