基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Gateway 如何实现动态路由?

知识点图片

Spring Cloud Gateway 实现动态路由的核心思想是:在应用运行期间,通过外部配置中心(如 Nacos、Apollo、Redis 或数据库)获取最新的路由配置,并将这些配置动态更新到 Gateway 的路由内存中,最后触发路由刷新事件使之生效,全程无需重启服务。

下面我将从核心原理主流实现方案以及基于 Nacos 的完整实现步骤三个方面为你详细解答。


一、 动态路由的核心原理

Gateway 内部维护路由主要依赖以下几个核心组件:

  1. RouteDefinition:路由定义的实体类(包含 id、uri、predicates、filters 等),也就是我们在 yml 中配置的那些属性。
  2. RouteDefinitionRepository:路由定义的存储仓库。默认情况下,Gateway 使用 InMemoryRouteDefinitionRepository 将路由存在内存中。
  3. RouteDefinitionWriter:用于向仓库中添加或删除路由定义。
  4. ApplicationEventPublisher:Spring 的事件发布器。当路由数据更新后,必须发布一个 RefreshRoutesEvent 事件,通知 Gateway 重新加载路由。

动态路由的标准流程:
监听外部数据源变化 -> 解析为 RouteDefinition 对象 -> 调用 RouteDefinitionWriter 更新/删除内存中的路由 -> 发布 RefreshRoutesEvent 事件。


二、 最常见/最推荐的方案:基于 Nacos 实现

在 Spring Cloud Alibaba 生态中,结合 Nacos 作为配置中心 是实现动态路由最主流的做法。

1. 准备 Nacos 上的路由配置

在 Nacos 中新建一个配置文件(例如 gateway-routes.json),内容为 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 依赖:

xml
<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:更新路由并发布刷新事件。

java
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

java
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 代码实现,还有以下几种常见做法:

  1. Spring Cloud Config 原生支持 (零代码)
    如果你使用的是新版的 Spring Cloud Gateway + Nacos Config,直接在 Nacos 的 yaml 文件中配置 spring.cloud.gateway.routes,由于 Spring Cloud 内部的自动刷新机制(@RefreshScope 相关的 EnvironmentChangeEvent),有时可以无需写任何 Java 代码即可实现动态刷新。但这种方式对于复杂逻辑(如只删除部分路由)控制力较弱,所以企业中多采用上述手写监听器的方式。

  2. 基于 Redis + 发布订阅机制

    • 将路由 JSON 存在 Redis 中。
    • 写一个后台管理系统修改 Redis 中的路由,同时向 Redis 发送一条 Pub/Sub 消息。
    • Gateway 节点监听 Redis 消息,收到消息后从 Redis 重新读取路由列表并执行 RefreshRoutesEvent
    • 适用场景:没有使用 Nacos/Apollo 等专业配置中心的小型项目。
  3. 基于数据库 (MySQL) + 定时任务/消息队列

    • 将路由规则设计成数据库表。
    • Gateway 启动时读取数据库加载路由。
    • 采用定时任务(例如每 10 秒查询一次数据库有没有 update_time 改变)或者搭配 MQ(数据库修改后发 MQ 通知 Gateway)来触发路由刷新。

四、 总结与注意事项

  • 本质:动态路由的本质是对 RouteDefinitionWriter 接口的调用以及 RefreshRoutesEvent 事件的发布。
  • 数据清理:在更新路由时,不仅要处理新增修改,千万别忘了处理删除。如果是全量同步,最好的做法是记录当前内存中的路由 ID,对比新获取的路由,找出需要删除的 ID 进行 routeDefinitionWriter.delete()
  • 高可用:Gateway 通常部署多个节点,配置中心(Nacos)的推送机制天生支持集群多节点同步更新,非常适合 Gateway 集群使用。
00:00
00:00