文章目录
-
-
- 一、动态路由的核心原理
- [二、基于 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 或通过事件刷新路由缓存:
- 路由规则存储在外部数据源(如 Nacos);
- 网关启动时从数据源加载初始路由;
- 当数据源中的路由规则变更时,通过监听机制(如 Nacos 配置变更通知)触发路由刷新;
- 调用 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之前加载):yamlspring: 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 ID :
gateway-service.yaml(与spring.application.name一致) -
Group :
DEFAULT_GROUP(默认) -
配置内容 (标准的 Gateway 路由规则):
yamlspring: 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. 测试动态路由
- 启动网关服务,验证初始路由是否生效(如访问
/api/user/1能否转发到user-service); - 在 Nacos 控制台修改路由规则(如新增一个路由或修改路径),无需重启网关;
- 再次访问新路由,验证是否生效(如新增
/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();
}
}
}
四、关键注意事项
- 路由ID唯一性 :确保路由
id唯一,避免更新时冲突; - 配置格式正确性 :动态路由的断言(
predicates)和过滤器(filters)格式需与 Gateway 要求一致,否则会加载失败; - 性能考量:频繁刷新路由可能影响网关性能,建议合理设置刷新间隔(如 Nacos 推送机制可实时更新,无需定时任务);
- 容错处理:解析路由配置时需添加异常处理,避免单个路由配置错误导致整体路由失效。
总结
Spring Cloud Gateway 实现动态路由的核心是将路由规则从静态配置迁移到外部数据源,并通过事件机制实时刷新路由缓存。基于 Nacos 等配置中心的方案适合需要频繁调整路由的场景,而基于数据库的方案适合需要通过业务系统管理路由的场景。两种方式均可实现无需重启网关即可更新路由,提升微服务架构的灵活性和可维护性。