Spring Gateway动态路由实现方案

前沿

Spring Cloud Gateway (下文简称 Gateway)作为微服务网关,动态配置路由是刚需,毕竟没人想路由一改就要重启网关。

本文结合源码,提供一种动态路由配置的思路。

何为路由

在 Gateway 中,路由对应的类是org.springframework.cloud.gateway.route.Route,各属性含义:

  • id:路由唯一标识
  • uri:转发的目标地址
  • order:路由顺序
  • predicate:谓词集合,符合条件的请求才会被路由
  • gatewayFilters:过滤器集合,允许对请求转发前后对请求和响应做修改
  • metadata:路由元数据
java 复制代码
public class Route implements Ordered {

	private final String id;

	private final URI uri;

	private final int order;

	private final AsyncPredicate<ServerWebExchange> predicate;

	private final List<GatewayFilter> gatewayFilters;

	private final Map<String, Object> metadata;
}

路由是怎么来的?

Gateway 提供了org.springframework.cloud.gateway.route.RouteLocator接口用来构建路由。

java 复制代码
public interface RouteLocator {
	Flux<Route> getRoutes();
}

子类有三个:

CachingRouteLocator

通过 ConcurrentHashMap 来缓存路由对象,避免路由对象反复构建,通过监听 RefreshRoutesEvent 事件来刷新缓存,主要用于提升性能。


CompositeRouteLocator

RouteLocator 的装饰器,将一组 RouteLocator 合并成一个。


RouteDefinitionRouteLocator

路由的实际构建者,根据路由定义对象 RouteDefinition 来构建路由,内部会依赖 RoutePredicateFactory,GatewayFilterFactory 工厂类。

RouteDefinition

为了构建路由,Gateway 提供了org.springframework.cloud.gateway.route.RouteDefinition路由定义类,根据 RouteDefinition 来构建 Route。

RouteDefinition 是怎么来的?

和构建路由一样,Gateway 也提供了 RouteDefinitionLocator 接口来发现 RouteDefinition。

java 复制代码
public interface RouteDefinitionLocator {
	Flux<RouteDefinition> getRouteDefinitions();
}

常见的子类有:

PropertiesRouteDefinitionLocator

基于配置文件的实现,它会从配置文件中读取spring.cloud.gateway.routes来构建 RouteDefinition,当然也支持从配置中心读取。

InMemoryRouteDefinitionRepository

基于内存的实现,内部持有一个 Map 对象来管理 RouteDefinition,默认是空的。额外实现了 RouteDefinitionWriter 接口,允许新增和删除 RouteDefinition。

DiscoveryClientRouteDefinitionLocator

基于服务发现的实现,支持从服务注册中心发现服务,并将服务构建成 RouteDefinition。

CompositeRouteDefinitionLocator

装饰器实现,用于将多个 RouteDefinitionLocator 合并成一个。

RouteLocator工作流程

路由的构建依赖两个核心组件:RouteLocator 和 RouteDefinitionLocator。

简单来说,RouteDefinitionLocator 负责提供 RouteDefinition 路由定义对象,RouteLocator 再根据 RouteDefinition 构建路由对象。

项目引入spring-cloud-starter-gateway依赖后,Spring 启动时会触发 Gateway 的自动装配,

org.springframework.cloud.gateway.config.GatewayAutoConfiguration类。

Gateway 路由定义发现依赖一组 RouteDefinitionLocator,再把他们包装成 CompositeRouteDefinitionLocator

java 复制代码
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
    return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}

默认的 RouteDefinitionLocator 有两个,分别是:PropertiesRouteDefinitionLocator 和 InMemoryRouteDefinitionRepository。前者读取配置文件来生成 RouteDefinition,后者使用 Map 来维护,默认是空的。

java 复制代码
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
    return new PropertiesRouteDefinitionLocator(properties);
}

@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
    return new InMemoryRouteDefinitionRepository();
}

RouteDefinitionLocator 有了,接下来就是 RouteLocator。默认会自动装配 RouteDefinitionRouteLocator 和 CachingRouteLocator,前者根据 RouteDefinition 来构建路由,后者提供缓存优化性能。

java 复制代码
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
        List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates,
        RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
    return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties,
            configurationService);
}

@Bean
@Primary
@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
    return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}

实现动态路由,CachingRouteLocator 是关键。

默认情况下,Gateway 会通过 CachingRouteLocator 来优化性能。因为根据 RouteDefinition 来构建路由还是有不小开销的,CachingRouteLocator 会把构建好的 List 缓存下来。

同时,它实现了 ApplicationListener,通过监听 RefreshRoutesEvent 事件来刷新路由。

动态路由实现

综上所述,实现动态路由的思路就是:

实现一个自定义的 RouteDefinitionLocator 从远程加载配置并构建 RouteDefinition,路由一旦有变更,就发送一个 RefreshRoutesEvent 事件通知 CachingRouteLocator 刷新路由,即可生效。

路由配置推荐 Nacos,因为它有变更通知。通过监听配置,即可在配置变更时,第一时间刷新路由,让新的路由生效。

首先,引入依赖

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>

然后,实现自定义的 RouteDefinitionLocator,目的是从 Nacos 读取路由配置,构建 RouteDefinition。这里以 JSON 格式配置,routesJson启动时由 Nacos 负责初始化配置,routesListener()方法在配置变更时触发,关键是变更后发送 RefreshRoutesEvent 事件,否则缓存不会刷新。

java 复制代码
@Component
public class DynamicRouteDefinitionLocator implements RouteDefinitionLocator {

    @NacosConfig(dataId = "gateway-routes.json", group = "DEFAULT_GROUP")
    private String routesJson;
    private final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    @NacosConfigListener(dataId = "gateway-routes.json", group = "DEFAULT_GROUP")
    public void routesListener(String data) {
        this.routesJson = data;
        // 发送路由刷新事件,getRouteDefinitions()会重新触发
        eventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        try {
            return Flux.fromIterable(OBJECT_MAPPER.readerForListOf(RouteDefinition.class).readValue(routesJson));
        } catch (JsonProcessingException e) {
            return Flux.empty();
        }
    }
}

在 Nacos 中配置,Data ID=gateway-routes.json,Group=DEFAULT_GROUP,内容如下:

json 复制代码
[
    {
        "id": "tb_route",
        "uri": "https://www.taobao.com",
        "predicates": [
            {
                "name": "Path",
                "args": {
                    "pattern": "/tb/**"
                }
            }
        ],
        "filters": [
            {
                "name": "StripPrefix",
                "args": {
                    "parts": "1"
                }
            }
        ]
    }
]

网关启动,访问127.0.0.1:8080/tb的请求会被转发到www.taobao.com

修改路由配置如下,网关无需重启,访问127.0.0.1:8080/jd的请求会被转发到www.jd.com

json 复制代码
[
     {
        "id": "jd_route",
        "uri": "https://www.jd.com/",
        "predicates": [
            {
                "name": "Path",
                "args": {
                    "pattern": "/jd/**"
                }
            }
        ],
        "filters": [
            {
                "name": "StripPrefix",
                "args": {
                    "parts": "1"
                }
            }
        ]
    }
]

尾巴

Spring Cloud Gateway 动态路由实现的核心思路,是通过自定义 RouteDefinitionLocator 从 Nacos 等配置中心动态加载路由定义,并在配置变更时发布 RefreshRoutesEvent 事件触发 CachingRouteLocator 刷新路由缓存。

相关推荐
golang学习记2 小时前
国内完美安装 Rust 环境 + VSCode 编写 Hello World 完整指南(2025 最新)
后端
Undoom2 小时前
解锁超级生产力:手把手教你构建与GitHub深度集成的自动化工作流,让AI成为你的编程副驾驶
后端
我是华为OD~HR~栗栗呀3 小时前
前端面经-高级开发(华为od)
java·前端·后端·python·华为od·华为·面试
熙客3 小时前
分布式ID解决方案
java·分布式·spring cloud·微服务
菜鸟小九3 小时前
SSM(MybatisPlus)
java·开发语言·spring boot·后端
不爱编程的小九九4 小时前
小九源码-springboot051-智能推荐旅游平台
java·spring boot·后端
数据知道4 小时前
Go基础:常用数学函数处理(主要是math包rand包的处理)
开发语言·后端·golang·go语言
期待のcode4 小时前
MyBatis框架—延迟加载与多级缓存
java·数据库·后端·缓存·mybatis
数据知道4 小时前
Go基础:文件与文件夹操作详解
开发语言·后端·golang·go语言