如何使用Spring Cloud Gateway实现动态路由?
文章目录
- 一、动态路由的核心原理
- 二、基于 Nacos 实现动态路由(推荐)
- 1. 环境准备
- 2. 在 Nacos 中定义路由规则
- 3. 配置动态路由刷新机制
- 4. 测试动态路由
- 三、基于数据库实现动态路由(自定义数据源)
- 1. 数据库设计
- 2. 自定义 RouteDefinitionLocator
- 3. 定时刷新路由
- 四、关键注意事项
- 总结
在微服务场景中,服务实例可能频繁上下线,或需要动态调整路由规则(如灰度发布、临时路由),此时 动态路由(无需重启网关即可更新路由配置)就显得尤为重要。Spring Cloud Gateway 支持多种动态路由实现方式,核心思路是 将路由规则存储在外部数据源(如数据库、Nacos、Apollo 等),并通过事件监听或配置中心推送机制实时更新路由。
一、动态路由的核心原理
Spring Cloud Gateway 的路由信息由 RouteDefinitionLocator 接口提供,默认从配置文件(application.yml)加载。要实现动态路由,需自定义 RouteDefinitionLocator 或通过事件刷新路由缓存:
- 路由规则存储在外部数据源(如 Nacos);
- 网关启动时从数据源加载初始路由;
- 当数据源中的路由规则变更时,通过监听机制(如 Nacos 配置变更通知)触发路由刷新;
- 调用 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 IDcloud:nacos:config:server-addr: localhost:8848 # Nacos 服务器地址file-extension: yaml # 配置文件格式
2. 在 Nacos 中定义路由规则
登录 Nacos 控制台(http://localhost:8848),创建配置:
- Data ID:
gateway-service.yaml(与spring.application.name一致) - Group:
DEFAULT_GROUP(默认) - 配置内容(标准的 Gateway 路由规则):
spring:cloud:gateway:routes:- id: user-service-routeuri: lb://user-service # 负载均衡到 user-service 服务predicates:- Path=/api/user/**filters:- StripPrefix=1 # 去除路径前缀 /api- id: order-service-routeuri: lb://order-servicepredicates:- 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 {@Autowiredprivate NacosConfigManager nacosConfigManager;@Autowiredprivate RouteDefinitionWriter routeDefinitionWriter;private ApplicationEventPublisher publisher;// 路由配置的 Data ID(与 Nacos 中一致)private static final String DATA_ID = "gateway-service.yaml";// 路由配置的 Groupprivate static final String GROUP = "DEFAULT_GROUP";@PostConstructpublic 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() {@Overridepublic void receiveConfigInfo(String configInfo) {// 配置变更时重新加载路由loadRouteConfig(configInfo);}@Overridepublic 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;}@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.publisher = applicationEventPublisher;}
}
关键逻辑:
- 初始化时从 Nacos 加载路由配置并添加到网关;
- 监听 Nacos 配置变更,当路由规则更新时,先删除旧路由,再添加新路由,最后发布
RefreshRoutesEvent事件触发网关刷新。
4. 测试动态路由
- 启动网关服务,验证初始路由是否生效(如访问
/api/user/1能否转发到user-service); - 在 Nacos 控制台修改路由规则(如新增一个路由或修改路径),无需重启网关;
- 再次访问新路由,验证是否生效(如新增
/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 {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic 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 {@Autowiredprivate DbRouteDefinitionLocator dbRouteLocator;@Autowiredprivate RouteDefinitionWriter routeWriter;@Autowiredprivate 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();}}
}
四、关键注意事项
- 路由ID唯一性:确保路由
id唯一,避免更新时冲突; - 配置格式正确性:动态路由的断言(
predicates)和过滤器(filters)格式需与 Gateway 要求一致,否则会加载失败; - 性能考量:频繁刷新路由可能影响网关性能,建议合理设置刷新间隔(如 Nacos 推送机制可实时更新,无需定时任务);
- 容错处理:解析路由配置时需添加异常处理,避免单个路由配置错误导致整体路由失效。
总结
Spring Cloud Gateway 实现动态路由的核心是将路由规则从静态配置迁移到外部数据源,并通过事件机制实时刷新路由缓存。基于 Nacos 等配置中心的方案适合需要频繁调整路由的场景,而基于数据库的方案适合需要通过业务系统管理路由的场景。两种方式均可实现无需重启网关即可更新路由,提升微服务架构的灵活性和可维护性。
