如何使用Spring Cloud Gateway实现动态路由?

文章目录

      • 一、动态路由的核心原理
      • [二、基于 Nacos 实现动态路由(推荐)](#二、基于 Nacos 实现动态路由(推荐))
        • [1. 环境准备](#1. 环境准备)
        • [2. 在 Nacos 中定义路由规则](#2. 在 Nacos 中定义路由规则)
        • [3. 配置动态路由刷新机制](#3. 配置动态路由刷新机制)
        • [4. 测试动态路由](#4. 测试动态路由)
      • 三、基于数据库实现动态路由(自定义数据源)
        • [1. 数据库设计](#1. 数据库设计)
        • [2. 自定义 RouteDefinitionLocator](#2. 自定义 RouteDefinitionLocator)
        • [3. 定时刷新路由](#3. 定时刷新路由)
      • 四、关键注意事项
      • 总结

在微服务场景中,服务实例可能频繁上下线,或需要动态调整路由规则(如灰度发布、临时路由),此时 动态路由 (无需重启网关即可更新路由配置)就显得尤为重要。Spring Cloud Gateway 支持多种动态路由实现方式,核心思路是 将路由规则存储在外部数据源(如数据库、Nacos、Apollo 等),并通过事件监听或配置中心推送机制实时更新路由

一、动态路由的核心原理

Spring Cloud Gateway 的路由信息由 RouteDefinitionLocator 接口提供,默认从配置文件(application.yml)加载。要实现动态路由,需自定义 RouteDefinitionLocator 或通过事件刷新路由缓存

  1. 路由规则存储在外部数据源(如 Nacos);
  2. 网关启动时从数据源加载初始路由;
  3. 当数据源中的路由规则变更时,通过监听机制(如 Nacos 配置变更通知)触发路由刷新;
  4. 调用 Gateway 提供的 RouteDefinitionWriter 接口更新内存中的路由,并发布 RefreshRoutesEvent 事件通知网关重新加载路由。

二、基于 Nacos 实现动态路由(推荐)

Nacos 作为配置中心,支持配置变更实时推送,是实现动态路由的理想选择。以下是详细步骤:

1. 环境准备
  • 引入依赖(Maven):

    xml 复制代码
    <!-- Spring Cloud Gateway 核心 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
    <!-- Nacos 配置中心(用于存储路由规则) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    
    <!-- Nacos 服务发现(可选,用于服务名路由) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  • 配置 Nacos 地址(bootstrap.yml,需在 application.yml 之前加载):

    yaml 复制代码
    spring:
      application:
        name: gateway-service  # 服务名,对应 Nacos 配置的 Data ID
      cloud:
        nacos:
          config:
            server-addr: localhost:8848  # Nacos 服务器地址
            file-extension: yaml  # 配置文件格式
2. 在 Nacos 中定义路由规则

登录 Nacos 控制台(http://localhost:8848),创建配置:

  • Data IDgateway-service.yaml(与 spring.application.name 一致)

  • GroupDEFAULT_GROUP(默认)

  • 配置内容 (标准的 Gateway 路由规则):

    yaml 复制代码
    spring:
      cloud:
        gateway:
          routes:
            - id: user-service-route
              uri: lb://user-service  # 负载均衡到 user-service 服务
              predicates:
                - Path=/api/user/**
              filters:
                - StripPrefix=1  # 去除路径前缀 /api
    
            - id: order-service-route
              uri: lb://order-service
              predicates:
                - Path=/api/order/**
              filters:
                - StripPrefix=1
3. 配置动态路由刷新机制

Nacos 配置变更时,会自动推送新配置到网关,需通过 @RefreshScope 使路由配置生效。但默认情况下,Gateway 不会自动刷新路由缓存,需自定义配置类监听配置变更并刷新路由:

java 复制代码
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosPropertySourceRepository;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.beans.factory.annotation.Autowired;
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.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.Executor;

@Configuration
public class NacosDynamicRouteConfig implements ApplicationEventPublisherAware {

    @Autowired
    private NacosConfigManager nacosConfigManager;

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    // 路由配置的 Data ID(与 Nacos 中一致)
    private static final String DATA_ID = "gateway-service.yaml";
    // 路由配置的 Group
    private static final String GROUP = "DEFAULT_GROUP";

    @PostConstruct
    public void init() throws NacosException {
        // 1. 初始化加载路由配置
        String configInfo = nacosConfigManager.getConfigService().getConfig(DATA_ID, GROUP, 5000);
        loadRouteConfig(configInfo);

        // 2. 监听 Nacos 配置变更
        nacosConfigManager.getConfigService().addListener(DATA_ID, GROUP, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                // 配置变更时重新加载路由
                loadRouteConfig(configInfo);
            }

            @Override
            public Executor getExecutor() {
                return null;
            }
        });
    }

    // 解析配置并更新路由
    private void loadRouteConfig(String configInfo) {
        try {
            // 从配置中解析出 RouteDefinition 列表(需自定义解析逻辑,或借助 Spring 配置绑定)
            List<RouteDefinition> routeDefinitions = parseRouteDefinitions(configInfo);

            // 先清空旧路由
            routeDefinitionWriter.delete(Mono.just("*")).block();

            // 再添加新路由
            if (!CollectionUtils.isEmpty(routeDefinitions)) {
                routeDefinitions.forEach(route -> {
                    routeDefinitionWriter.save(Mono.just(route)).block();
                });
            }

            // 发布刷新事件,通知网关更新路由
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 解析配置字符串为 RouteDefinition 列表(需根据实际配置格式实现)
    private List<RouteDefinition> parseRouteDefinitions(String configInfo) {
        // 示例:使用 Spring 的 YamlPropertiesFactoryBean 解析配置
        // 实际需根据 configInfo 中的内容提取 spring.cloud.gateway.routes 节点
        // 此处省略具体解析逻辑,可参考 Spring Cloud Gateway 的配置绑定方式
        return null;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}

关键逻辑:

  • 初始化时从 Nacos 加载路由配置并添加到网关;
  • 监听 Nacos 配置变更,当路由规则更新时,先删除旧路由,再添加新路由,最后发布 RefreshRoutesEvent 事件触发网关刷新。
4. 测试动态路由
  1. 启动网关服务,验证初始路由是否生效(如访问 /api/user/1 能否转发到 user-service);
  2. 在 Nacos 控制台修改路由规则(如新增一个路由或修改路径),无需重启网关;
  3. 再次访问新路由,验证是否生效(如新增 /api/product/** 路由后,访问该路径能否转发到 product-service)。

三、基于数据库实现动态路由(自定义数据源)

若需更灵活的路由管理(如通过后台系统增删改路由),可将路由规则存储在数据库(如 MySQL),通过定时任务或事件监听刷新路由。

1. 数据库设计

创建路由规则表(gateway_route):

sql 复制代码
CREATE TABLE `gateway_route` (
  `id` varchar(64) NOT NULL COMMENT '路由ID',
  `uri` varchar(255) NOT NULL COMMENT '目标地址(如 lb://user-service)',
  `predicates` text COMMENT '断言规则(JSON格式,如 [{"name":"Path","args":{"pattern":"/api/user/**"}}])',
  `filters` text COMMENT '过滤器规则(JSON格式)',
  `order` int(11) DEFAULT 0 COMMENT '路由优先级(值越小越优先)',
  `status` tinyint(1) DEFAULT 1 COMMENT '状态(1-启用,0-禁用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 自定义 RouteDefinitionLocator

实现 RouteDefinitionLocator 接口,从数据库加载路由:

java 复制代码
@Configuration
public class DbRouteDefinitionLocator implements RouteDefinitionLocator {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // 从数据库查询启用的路由
        List<RouteDefinition> routes = jdbcTemplate.query(
            "SELECT id, uri, predicates, filters, `order` FROM gateway_route WHERE status = 1",
            (rs, rowNum) -> {
                RouteDefinition route = new RouteDefinition();
                route.setId(rs.getString("id"));
                route.setUri(URI.create(rs.getString("uri")));
                route.setOrder(rs.getInt("order"));

                // 解析断言(JSON -> List<PredicateDefinition>)
                String predicatesJson = rs.getString("predicates");
                List<PredicateDefinition> predicates = JSON.parseArray(predicatesJson, PredicateDefinition.class);
                route.setPredicates(predicates);

                // 解析过滤器(JSON -> List<FilterDefinition>)
                String filtersJson = rs.getString("filters");
                List<FilterDefinition> filters = JSON.parseArray(filtersJson, FilterDefinition.class);
                route.setFilters(filters);

                return route;
            }
        );
        return Flux.fromIterable(routes);
    }
}
3. 定时刷新路由

通过定时任务定期从数据库加载最新路由并刷新:

java 复制代码
@Component
public class DbRouteRefreshTask {

    @Autowired
    private DbRouteDefinitionLocator dbRouteLocator;

    @Autowired
    private RouteDefinitionWriter routeWriter;

    @Autowired
    private ApplicationEventPublisher publisher;

    // 每30秒刷新一次
    @Scheduled(fixedRate = 30000)
    public void refreshRoutes() {
        try {
            // 清空旧路由
            routeWriter.delete(Mono.just("*")).block();

            // 加载新路由
            dbRouteLocator.getRouteDefinitions().collectList().block()
                .forEach(route -> routeWriter.save(Mono.just(route)).block());

            // 发布刷新事件
            publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四、关键注意事项

  1. 路由ID唯一性 :确保路由 id 唯一,避免更新时冲突;
  2. 配置格式正确性 :动态路由的断言(predicates)和过滤器(filters)格式需与 Gateway 要求一致,否则会加载失败;
  3. 性能考量:频繁刷新路由可能影响网关性能,建议合理设置刷新间隔(如 Nacos 推送机制可实时更新,无需定时任务);
  4. 容错处理:解析路由配置时需添加异常处理,避免单个路由配置错误导致整体路由失效。

总结

Spring Cloud Gateway 实现动态路由的核心是将路由规则从静态配置迁移到外部数据源,并通过事件机制实时刷新路由缓存。基于 Nacos 等配置中心的方案适合需要频繁调整路由的场景,而基于数据库的方案适合需要通过业务系统管理路由的场景。两种方式均可实现无需重启网关即可更新路由,提升微服务架构的灵活性和可维护性。

相关推荐
阿琦学代码6 小时前
Spring Cloud(微服务) 概述
后端·spring·spring cloud
.柒宇.7 小时前
《云岚到家》第一章个人总结
spring boot·spring·spring cloud
小任今晚几点睡8 小时前
kubernetes的微服务
微服务·容器·kubernetes
精神小伙就是猛8 小时前
.Net Core基于EasyCore.EventBus实现事件总线
微服务·.netcore
百度智能云技术站8 小时前
百度亮相 SREcon25:搜索稳定背后的秘密,微服务雪崩故障防范
微服务·架构·dubbo
bxlj_jcj18 小时前
Nacos注册中心:从服务注册到负载均衡
spring cloud·nacos
百度Geek说1 天前
大规模微服务系统中的雪崩故障防治
微服务
qq_5470261791 天前
SpringCloud--Sleuth 分布式链路追踪
后端·spring·spring cloud