java

关注公众号 jb51net

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

Spring Gateway动态路由的实现方案

作者:程序员小潘

Spring Cloud Gateway (下文简称 Gateway)作为微服务网关,动态配置路由是刚需,毕竟没人想路由一改就要重启网关,本文结合源码,提供一种动态路由配置的思路,需要的朋友可以参考下

前沿

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

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

何为路由

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

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接口用来构建路由。

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。

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

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

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

@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 来构建路由,后者提供缓存优化性能。

@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,因为它有变更通知。通过监听配置,即可在配置变更时,第一时间刷新路由,让新的路由生效。

首先,引入依赖

<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 事件,否则缓存不会刷新。

@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,内容如下:

[
    {
        "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

[
     {
        "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 刷新路由缓存。

以上就是Spring Gateway动态路由的实现方案的详细内容,更多关于Spring Gateway动态路由的资料请关注脚本之家其它相关文章!

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