Gateway 如何实现动态路由?
Spring Cloud Gateway 实现动态路由的核心思想是:在应用运行期间,通过外部配置中心(如 Nacos、Apollo、Redis 或数据库)获取最新的路由配置,并将这些配置动态更新到 Gateway 的路由内存中,最后触发路由刷新事件使之生效,全程无需重启服务。
下面我将从核心原理、主流实现方案以及基于 Nacos 的完整实现步骤三个方面为你详细解答。
一、 动态路由的核心原理
Gateway 内部维护路由主要依赖以下几个核心组件:
RouteDefinition:路由定义的实体类(包含 id、uri、predicates、filters 等),也就是我们在 yml 中配置的那些属性。RouteDefinitionRepository:路由定义的存储仓库。默认情况下,Gateway 使用InMemoryRouteDefinitionRepository将路由存在内存中。RouteDefinitionWriter:用于向仓库中添加或删除路由定义。ApplicationEventPublisher:Spring 的事件发布器。当路由数据更新后,必须发布一个RefreshRoutesEvent事件,通知 Gateway 重新加载路由。
动态路由的标准流程:
监听外部数据源变化 -> 解析为 RouteDefinition 对象 -> 调用 RouteDefinitionWriter 更新/删除内存中的路由 -> 发布 RefreshRoutesEvent 事件。
二、 最常见/最推荐的方案:基于 Nacos 实现
在 Spring Cloud Alibaba 生态中,结合 Nacos 作为配置中心 是实现动态路由最主流的做法。
1. 准备 Nacos 上的路由配置
在 Nacos 中新建一个配置文件(例如 gateway-routes.json),内容为 JSON 格式的路由数组:
[
{
"id": "user-service-route",
"uri": "lb://user-service",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/user/"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"parts": "1"
}
}
]
}
]
2. 引入依赖
在 Gateway 项目的 pom.xml 中引入 Nacos Config 和 Gateway 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.x</version> <!-- 用于解析 JSON -->
</dependency>
3. 编写动态路由更新服务 (核心代码)
封装一个服务,用来操作 Gateway 的 API:更新路由并发布刷新事件。
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.List;
@Service
public class DynamicRouteService implements ApplicationEventPublisherAware {
private final RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter) {
this.routeDefinitionWriter = routeDefinitionWriter;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 更新路由列表
*/
public void updateList(List<RouteDefinition> routeDefinitions) {
routeDefinitions.forEach(this::updateRoute);
// 核心:发布刷新路由事件
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* 更新单个路由 (先删除后添加)
*/
private void updateRoute(RouteDefinition definition) {
try {
// 先尝试删除老路由
this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
} catch (Exception e) {
// 如果老路由不存在会报错,捕获即可
}
// 添加新路由
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
}
}
4. 编写 Nacos 配置监听器
监听 Nacos 中 gateway-routes.json 的变化,一旦变化,将 JSON 解析并传给上面的 DynamicRouteService。
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.Executor;
@Component
public class NacosDynamicRouteListener {
private final NacosConfigManager nacosConfigManager;
private final DynamicRouteService dynamicRouteService;
// Nacos 中的 DataId 和 Group
private static final String DATA_ID = "gateway-routes.json";
private static final String GROUP = "DEFAULT_GROUP";
public NacosDynamicRouteListener(NacosConfigManager nacosConfigManager, DynamicRouteService dynamicRouteService) {
this.nacosConfigManager = nacosConfigManager;
this.dynamicRouteService = dynamicRouteService;
}
@PostConstruct
public void dynamicRouteByNacosListener() {
try {
// 1. 项目启动时,先拉取一次配置并初始化路由
String configInfo = nacosConfigManager.getConfigService().getConfig(DATA_ID, GROUP, 5000);
updateRoutes(configInfo);
// 2. 注册监听器,监听配置的动态变化
nacosConfigManager.getConfigService().addListener(DATA_ID, GROUP, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
// 监听到配置变化后,更新路由
updateRoutes(configInfo);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void updateRoutes(String configInfo) {
if (configInfo != null && !configInfo.isEmpty()) {
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
dynamicRouteService.updateList(routeDefinitions);
System.out.println("路由已动态更新,当前路由数量: " + routeDefinitions.size());
}
}
}
三、 其他实现方案简述
除了 Nacos 代码实现,还有以下几种常见做法:
Spring Cloud Config 原生支持 (零代码):
如果你使用的是新版的 Spring Cloud Gateway + Nacos Config,直接在 Nacos 的 yaml 文件中配置spring.cloud.gateway.routes,由于 Spring Cloud 内部的自动刷新机制(@RefreshScope相关的EnvironmentChangeEvent),有时可以无需写任何 Java 代码即可实现动态刷新。但这种方式对于复杂逻辑(如只删除部分路由)控制力较弱,所以企业中多采用上述手写监听器的方式。基于 Redis + 发布订阅机制:
- 将路由 JSON 存在 Redis 中。
- 写一个后台管理系统修改 Redis 中的路由,同时向 Redis 发送一条 Pub/Sub 消息。
- Gateway 节点监听 Redis 消息,收到消息后从 Redis 重新读取路由列表并执行
RefreshRoutesEvent。 - 适用场景:没有使用 Nacos/Apollo 等专业配置中心的小型项目。
基于数据库 (MySQL) + 定时任务/消息队列:
- 将路由规则设计成数据库表。
- Gateway 启动时读取数据库加载路由。
- 采用定时任务(例如每 10 秒查询一次数据库有没有
update_time改变)或者搭配 MQ(数据库修改后发 MQ 通知 Gateway)来触发路由刷新。
四、 总结与注意事项
- 本质:动态路由的本质是对
RouteDefinitionWriter接口的调用以及RefreshRoutesEvent事件的发布。 - 数据清理:在更新路由时,不仅要处理新增和修改,千万别忘了处理删除。如果是全量同步,最好的做法是记录当前内存中的路由 ID,对比新获取的路由,找出需要删除的 ID 进行
routeDefinitionWriter.delete()。 - 高可用:Gateway 通常部署多个节点,配置中心(Nacos)的推送机制天生支持集群多节点同步更新,非常适合 Gateway 集群使用。