java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Cloud Gateway动态路由

使用Spring Cloud Gateway实现动态路由的核心原理

作者:青鱼入云

Spring Cloud Gateway 实现动态路由的核心是将路由规则从静态配置迁移到外部数据源,并通过事件机制实时刷新路由缓存,本文介绍使用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):

<!-- 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 之前加载):

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),创建配置:

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 不会自动刷新路由缓存,需自定义配置类监听配置变更并刷新路由:

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;
    }
}

关键逻辑:

4. 测试动态路由

  1. 启动网关服务,验证初始路由是否生效(如访问 /api/user/1 能否转发到 user-service);
  2. 在 Nacos 控制台修改路由规则(如新增一个路由或修改路径),无需重启网关;
  3. 再次访问新路由,验证是否生效(如新增 /api/product/** 路由后,访问该路径能否转发到 product-service)。

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

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

1. 数据库设计

创建路由规则表(gateway_route):

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 接口,从数据库加载路由:

@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. 定时刷新路由

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

@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 等配置中心的方案适合需要频繁调整路由的场景,而基于数据库的方案适合需要通过业务系统管理路由的场景。两种方式均可实现无需重启网关即可更新路由,提升微服务架构的灵活性和可维护性。

到此这篇关于如何使用Spring Cloud Gateway实现动态路由?的文章就介绍到这了,更多相关Spring Cloud Gateway动态路由内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文