三分钟了解Spring Cloud Gateway路由转发之自动路由

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 一、前言今天有个新同学,问我为什么我们的网关服务Spring Cloud Gateway,没有配置路由就可以将请求到路由服务,说他们之前的项目的网关是将路由配置在配置文件中。指定路由类似以下写法。而在现在的项目的配置文件中未发现任何路由配置。觉得很奇怪,Spring-Cloud-Gateway 是如何将请求路由到指定的服务的呢。我让他比对一下配置文件有什么不同,他说就是只有一个spring.cloud.gateway.discovery.locator.enabled=true

大家好,我是冰点,今天和大家分享一下关于Spring Cloud Gateway 利用服务注册与发现实现自动路由的原理和源码解读。希望对大家有所帮助。

image.png

一、前言

今天有个新同学,问我为什么我们的网关服务Spring Cloud Gateway,没有配置路由就可以将请求到路由服务,说他们之前的项目的网关是将路由配置在配置文件中。指定路由类似以下写法。而在现在的项目的配置文件中未发现任何路由配置。觉得很奇怪,Spring-Cloud-Gateway 是如何将请求路由到指定的服务的呢。我让他比对一下配置文件有什么不同,他说就是只有一个spring.cloud.gateway.discovery.locator.enabled=true

如下配置一般是大多数项目配置路由的我们一般称之为静态路由,是由配置文件硬编码后在程序启动的时候加载的。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          lower-case-service-id: true # 忽略服务名的大小写
      routes:
        - id: service1
          uri: lb://service1
          predicates:
            - Path=/service1/**
        - id: service2
          uri: lb://service2
          predicates:
            - Path=/service2/**


除了上述的路由配置外,其实我们通俗的将gateway 的路由可以分为三种

  1. 静态路由
  2. 动态路由
  3. 自动路由

下面我们详细了解一下这三种路由

Spring Cloud Gateway 支持三种类型的路由:静态路由、动态路由和自动路由。

二、路由配置

1. 静态路由

静态路由是指在配置文件中预先定义好的路由规则,它们在应用启动时就已经存在。静态路由的优点是可以快速定位和处理请求,缺点是需要手动配置,不支持动态添加、修改和删除路由规则。

在 Spring Cloud Gateway 中,可以通过配置文件来定义静态路由规则。例如:

spring:
  cloud:
    gateway:
      routes:
        - id: service1
          uri: http://localhost:8081
          predicates:
            - Path=/service1/**
        - id: service2
          uri: http://localhost:8082
          predicates:
            - Path=/service2/**

这段配置文件定义了两个静态路由规则,分别对应于服务 service1 和服务 service2。当请求的路径匹配 /service1/** 时,它就会被转发到 http://localhost:8081;当请求的路径匹配 /service2/** 时,它就会被转发到 http://localhost:8082。

2. 动态路由

动态路由是指在运行时动态添加、修改和删除路由规则,可以根据不同的条件动态地调整路由规则,例如根据请求路径、请求头、请求参数等条件。动态路由的优点是可以根据实际情况调整路由规则,缺点是需要额外的管理和维护成本。

在 Spring Cloud Gateway 中,可以通过 API 来动态添加、修改和删除路由规则。例如

@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
public void addRoute(String id, String uri, String predicates) {
    RouteDefinition routeDefinition = new RouteDefinition();
    routeDefinition.setId(id);
    routeDefinition.setUri(URI.create(uri));
    routeDefinition.setPredicates(Collections.singletonList(new PredicateDefinition(predicates)));
    routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
}
public void updateRoute(String id, String uri, String predicates) {
    RouteDefinition routeDefinition = new RouteDefinition();
    routeDefinition.setId(id);
    routeDefinition.setUri(URI.create(uri));
    routeDefinition.setPredicates(Collections.singletonList(new PredicateDefinition(predicates)));
    routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).then(Mono.defer(() -> {
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        return Mono.empty();
    })).subscribe();
}
public void deleteRoute(String id) {
    routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}

通过注入 RouteDefinitionWriter 对象来操作路由规则。 addRoute 方法可以添加一条路由规则, updateRoute 方法可以修改一条路由规则, deleteRoute 方法可以删除一条路由规则。这些操作会实时生效,不需要重启应用。需要在 Spring Boot 应用启动时加载 RouteDefinitionLocator 对象,以便正确加载动态路由规则。

3. 自动路由

自动路由是指根据服务注册中心的服务信息自动生成路由规则。当有新的服务上线或下线时,路由规则也会自动更新。自动路由的优点是可以根据实际情况自动调整路由规则,缺点是需要服务注册中心的支持。其实服务发现可以支持很多种,主要实现spring cloud 提供的接口即可。下次我专门写一篇介绍


服务发现功能的实现可以通过 Spring Cloud Commons 中的 DiscoveryClient 类实现。Spring Cloud

Discovery 可以与多种服务发现组件集成,包括 Eureka、Consul、Zookeeper 等。Spring Cloud

Gateway 会自动与 Spring Cloud Discovery 集成,可以使用 Spring Cloud Discovery

来获取服务实例列表,并将这些服务实例转换为路由规则。大家感兴趣可以先了解一下这两个接口。

3eb9538324d24fd08565faea02f001f1.png

在 Spring Cloud Gateway 中,可以通过配置服务注册中心来启用自动路由功能。例如:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

启用了服务发现功能,并将服务 ID 转换为小写

三、Spring Cloud Gateway 是如何实现动态路由

客户端发送请求到Spring Cloud Gateway,Gateway Handler Mapping确定请求与路由匹配,则会将请求交给Gateway Web Handler处理。

工作原理

图片来源spring官网 https://cloud.spring.io/spring-cloud-gateway/reference/html/

eee46c4da9ef4f36be1b09f559c5f8d5.png

源码解析

Spring Cloud Gateway 是一款基于 Spring Framework 和 Spring Boot 的网关框架,它提供了统一的路由转发、负载均衡、请求过滤和请求转换等功能。在 Spring Cloud Gateway 中,路由转发是其中最核心的功能之一。


下面是 Spring Cloud Gateway 路由转发的原理和源码解析。

路由转发原理

Spring Cloud Gateway 的路由转发基于 Netty 和 Reactor 实现。当一个请求到达 Spring Cloud

Gateway 时,它会首先经过一系列过滤器的处理,然后根据路由规则将请求转发到正确的目标地址。


路由规则由路由配置组件管理,它可以通过多种方式来创建,例如基于配置文件的路由配置、基于 Java代码的路由配置、基于服务发现的路由配置等。每个路由规则包含一个路由条件和一个目标 URI,当一个请求满足路由条件时,它就会被转发到目标

URI。


路由条件由路由规则的路由条件工厂类创建,例如

PathRoutePredicateFactory、HeaderRoutePredicateFactory、MethodRoutePredicateFactory等。它们可以根据请求的路径、请求头、请求方法等条件来判断一个请求是否满足路由条件。


目标 URI 可以通过多种方式指定,例如硬编码的 URI、基于服务发现的 URI、基于请求头的 URI 等。在确定了目标 URI 后,Spring Cloud Gateway 会将请求转发到目标 URI,并将响应返回给客户端。

路由转发源码解析

在 Spring Cloud Gateway 中,路由转发的核心代码位于 org.springframework.cloud.gateway.handler 包中。其中,RoutePredicateHandlerMapping 类是 Spring Cloud Gateway 的路由转发入口,它继承了 AbstractHandlerMapping 类,并实现了其中的 getHandlerInternal 方法。

RoutePredicateHandlerMapping 的源码解析 为了方便理解,添加了中文注释

public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
    private final Map<String, RoutePredicateFactory> predicates; // 路由条件工厂类映射表
    private final GatewayFilterHandlerFilter filterHandlerFilter; // 过滤器处理器
    private final Map<String, Object> globalFilters; // 全局过滤器映射表
    private final RouteDefinitionLocator routeDefinitionLocator; // 路由规则定位器
    public RoutePredicateHandlerMapping(List<RoutePredicateFactory> predicates,
                                        GatewayFilterHandlerFilter filterHandlerFilter,
                                        List<GlobalFilter> globalFilters,
                                        RouteDefinitionLocator routeDefinitionLocator) {
        this.predicates = predicates.stream()
                .collect(Collectors.toMap(RoutePredicateFactory::name, Function.identity())); // 将路由条件工厂类列表转换为路由条件工厂类映射表
        this.filterHandlerFilter = filterHandlerFilter;
        this.globalFilters = globalFilters.stream()
                .collect(Collectors.toMap(GlobalFilter::name, Function.identity())); // 将全局过滤器列表转换为全局过滤器映射表
        this.routeDefinitionLocator = routeDefinitionLocator;
        setOrder(-1); // 设置路由转发的优先级
    }
    @Override
    protected Object getHandlerInternal(ServerHttpRequest request) throws Exception {
        List<RouteDefinition> definitions = this.routeDefinitionLocator.getRouteDefinitions().collectList().block(); // 获取所有路由规则
        if (definitions == null) { // 如果路由规则列表为空,则返回 null
            return null;
        }
        for (RouteDefinition routeDefinition : definitions) { // 遍历所有路由规则
            RoutePredicateFactory predicate = this.predicates.get(routeDefinition.getPredicate().getName());            RoutePredicate routePredicate = predicate.apply(routeDefinition.getPredicate().getArgs()); // 创建路由条件
            if (routePredicate.test(request)) { // 判断请求是否满足路由条件
                Route route = new Route(routeDefinition.getId(), routeDefinition.getUri(), routeDefinition.getFilters()); // 创建路由对象
                List<GatewayFilter> gatewayFilters = new ArrayList<>(routeDefinition.getFilters()); // 获取路由规则中的过滤器
                gatewayFilters.addAll(getGlobalFilters()); // 添加全局过滤器
                FilteringWebHandler filteringWebHandler = new FilteringWebHandler(new DefaultWebHandler(), new GatewayFilterChain(gatewayFilters)); // 创建过滤器链
                return new DefaultWebHandlerAdapter().handle(request, filteringWebHandler); // 返回路由转发处理器
            }
        }
        return null;
    }
    private Collection<Object> getGlobalFilters() {
        return this.globalFilters.values(); // 返回全局过滤器集合
    }
}

在 RoutePredicateHandlerMapping 中,首先通过构造方法初始化了路由条件工厂类映射表、过滤器处理器、全局过滤器映射表和路由规则定位器。然后,实现了 AbstractHandlerMapping 中的 getHandlerInternal 方法。在 getHandlerInternal 方法中,首先获取所有路由规则,并遍历每个路由规则。对于每个路由规则,将其路由条件工厂类名称作为 key,从路由条件工厂类映射表中获取对应的路由条件工厂类,并使用路由条件工厂类创建路由条件。然后,判断当前请求是否满足路由条件,如果满足,则创建路由对象,并获取路由规则中的过滤器和全局过滤器。将这些过滤器组成过滤器链,并将过滤器链和默认的 Web 处理器一起作为参数创建过滤器 Web 处理器。最后,使用过滤器 Web 处理器和当前请求创建 DefaultWebHandlerAdapter 的实例,并返回路由转发处理器。


写到这儿其实我们只是了解了一个请求在路由到后台服务之前必须要要经过的几道工序,就如同我最开始从Spring 官网获得的工作原理图。

四 、问题核心

我们来回答最开始的那个问题。那么如果在不配置路由规则的Spring Cloud Gateway 服务中,网关是如何做的转发呢,这才是我们核心问题。

那就不得不说一个重要的核心的接口和实现类 位于spring-cloud-gateway-core-2.x.RELEASE 下。

RouteDefinitionLocator。其实里面就一个核心方法 getRouteDefinitions

993ea958b1fe4092a8bce9b2e4b03262.png

DiscoveryClientRouteDefinitionLocator源码解析

DiscoveryClientRouteDefinitionLocator 是RouteDefinitionLocator 实现类。是 Spring Cloud Gateway 提供的一个基于服务发现的路由规则定位器,它可以自动将服务实例列表转换为路由规则,从而实现基于服务发现的路由配置。

为了方便大家理解,我在源码上添加了一些注释

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
    // 服务发现客户端
    private final DiscoveryClient discoveryClient;
    // 路由规则转换器
    private final RouteDefinitionLocator routeDefinitionLocator;
    // 服务过滤器
    private final Predicate<ServiceInstance> predicate;
  // 默认使用所有服务实例
    public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator) {
        this(discoveryClient, routeDefinitionLocator, instance -> true); // 默认使用所有服务实例
    }
  // 第二个构造方法可以指定服务过滤器
    public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator, Predicate<ServiceInstance> predicate) {
        this.discoveryClient = discoveryClient;
        this.routeDefinitionLocator = routeDefinitionLocator;
        this.predicate = predicate;
    }
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(getServiceNames()) // 获取所有服务名称
                .flatMap(this::getRoutes) // 遍历每个服务名称,获取该服务的路由规则
                .flatMap(routeDefinitionLocator::getRouteDefinitions) // 转换路由规则
                .doOnNext(route -> logger.debug("RouteDefinition matched: " + route)); // 打印日志
    }
    private List<String> getServiceNames() {
        return discoveryClient.getServices(); // 获取服务名称列表
    }
    private Mono<RouteDefinition> getRoutes(String serviceName) {
        return Mono.just(new RouteDefinition()) // 创建一个新的 RouteDefinition 对象
                .flatMap(routeDefinition -> Flux.fromIterable(getInstances(serviceName))) // 获取该服务的所有实例
                .filter(predicate) // 过滤服务实例
                .map(this::getInstanceRoute) // 将服务实例转换为路由规则
                .doOnNext(route -> logger.debug("RouteDefinition created: " + route)) // 打印日志
                .reduce(new RouteDefinition(), this::mergeRouteDefinitions); // 合并所有路由规则
    }
    private List<ServiceInstance> getInstances(String serviceName) {
        return discoveryClient.getInstances(serviceName); // 获取指定服务的所有实例
    }
    private RouteDefinition getInstanceRoute(ServiceInstance instance) {
        RouteDefinition route = new RouteDefinition(); // 创建一个新的 RouteDefinition 对象
        route.setId(instance.getServiceId()); // 设置路由规则的 ID 为服务名称
        URI uri = instance.getUri(); // 获取服务实例的 URI
        if (uri != null) {
            route.setUri(uri); // 设置路由规则的 URI
        }
        return route;
    }
    private RouteDefinition mergeRouteDefinitions(RouteDefinition route1, RouteDefinition route2) {
        route1.getFilters().addAll(route2.getFilters()); // 合并过滤器
        route1.getPredicates().addAll(route2.getPredicates()); // 合并谓词
        return route1;
    }
}

DiscoveryClientRouteDefinitionLocator 类的主要作用是从服务注册中心获取服务信息并将其转换为路由规则。它实现了 RouteDefinitionLocator 接口,用于获取路由规则列表。具体来说,它通过 DiscoveryClient 类获取所有服务名称,遍历每个服务名称,再通过 DiscoveryClient 类获取该服务的所有实例,最后将实例信息转换为路由规则。

getRouteDefinitions 方法是 DiscoveryClientRouteDefinitionLocator类的核心方法,用于获取所有的路由规则。它通过 Flux.fromIterable() 获取所有服务名称,然后通过 flatMap()方法遍历每个服务名称,获取该服务的路由规则。获取路由规则的方法是 getRoutes(),该方法通过 Mono.just()创建一个新的 RouteDefinition 对象,然后通过 Flux.fromIterable() 获取该服务的所有实例,再通过filter() 方法过滤服务实例,接着调用getInstanceRoute方法将服务实例转换为路由规则,最后通过reduce() 方法将所有路由规则合并成一个RouteDefinition 对象。在合并路由规则时,会调 mergeRouteDefinitions 方法实现合并过滤器和谓词的操作。

getServiceNames获取所有服务名称,它通过 DiscoveryClient 类的 getServices() 方法实现。

getInstances() 获取指定服务的所有实例,它通过 DiscoveryClient 类的 getInstances() 方法实现。

getInstanceRoute() 方法用于将服务实例转换为路由规则,它创建一个新的 RouteDefinition对象,将服务名称作为路由规则的 ID,将服务实例的 URI 作为路由规则的 URI,并返回该路由规则对象。

mergeRouteDefinitions方法用于合并路由规则,它将两个路由规则对象的过滤器和谓词合并到一个路由规则对象中,并返回该路由规则对象。

五、总结

所以总而言之要回答上面的问题,还是必须要有服务注册与发现的基础知识,才能理解。而实现这个特性的关键类=DiscoveryClientRouteDefinitionLocator 类,它通过服务发现客户端从服务注册中心获取服务信息并将其转换为路由规则,并实现了 RouteDefinitionLocator接口,用于获取路由规则列表。


好了今天的分享就到这儿,希望三分钟的阅读对你有所收获。我是冰点,下次再见。

目录
相关文章
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
173 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
111 62
|
12天前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
|
16天前
|
消息中间件 监控 Java
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
31 6
|
16天前
|
Java 关系型数据库 MySQL
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
36 5
|
16天前
|
缓存 监控 Java
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
27 5
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
71 2
|
19天前
|
负载均衡 Java API
项目中用的网关Gateway及SpringCloud
Spring Cloud Gateway 是一个功能强大、灵活易用的API网关解决方案。通过配置路由、过滤器、熔断器和限流等功能,可以有效地管理和保护微服务。本文详细介绍了Spring Cloud Gateway的基本概念、配置方法和实际应用,希望能帮助开发者更好地理解和使用这一工具。通过合理使用Spring Cloud Gateway,可以显著提升微服务架构的健壮性和可维护性。
25 0
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用
【10月更文挑战第8天】本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,通过 Spring Initializr 创建并配置 Spring Boot 项目,实现后端 API 和安全配置。接着,使用 Ant Design Pro Vue 脚手架创建前端项目,配置动态路由和菜单,并创建相应的页面组件。最后,通过具体实践心得,分享了版本兼容性、安全性、性能调优等注意事项,帮助读者快速搭建高效且易维护的应用框架。
53 3
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用
【10月更文挑战第7天】本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,通过 Spring Initializr 创建 Spring Boot 项目并配置 Spring Security。接着,实现后端 API 以提供菜单数据。在前端部分,使用 Ant Design Pro Vue 脚手架创建项目,并配置动态路由和菜单。最后,启动前后端服务,实现高效、美观且功能强大的应用框架。
60 2