大家好,我是冰点,今天和大家分享一下关于Spring Cloud Gateway 利用服务注册与发现实现自动路由的原理和源码解读。希望对大家有所帮助。
今天有个新同学,问我为什么我们的网关服务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 的路由可以分为三种
- 静态路由
- 动态路由
- 自动路由
下面我们详细了解一下这三种路由
Spring Cloud Gateway 支持三种类型的路由:静态路由、动态路由和自动路由。
静态路由是指在配置文件中预先定义好的路由规则,它们在应用启动时就已经存在。静态路由的优点是可以快速定位和处理请求,缺点是需要手动配置,不支持动态添加、修改和删除路由规则。
在 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。
动态路由是指在运行时动态添加、修改和删除路由规则,可以根据不同的条件动态地调整路由规则,例如根据请求路径、请求头、请求参数等条件。动态路由的优点是可以根据实际情况调整路由规则,缺点是需要额外的管理和维护成本。
在 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 对象,以便正确加载动态路由规则。
自动路由是指根据服务注册中心的服务信息自动生成路由规则。当有新的服务上线或下线时,路由规则也会自动更新。自动路由的优点是可以根据实际情况自动调整路由规则,缺点是需要服务注册中心的支持。其实服务发现可以支持很多种,主要实现spring cloud 提供的接口即可。下次我专门写一篇介绍
服务发现功能的实现可以通过 Spring Cloud Commons 中的 DiscoveryClient 类实现。Spring Cloud
Discovery 可以与多种服务发现组件集成,包括 Eureka、Consul、Zookeeper 等。Spring Cloud
Gateway 会自动与 Spring Cloud Discovery 集成,可以使用 Spring Cloud Discovery
来获取服务实例列表,并将这些服务实例转换为路由规则。大家感兴趣可以先了解一下这两个接口。
在 Spring Cloud Gateway 中,可以通过配置服务注册中心来启用自动路由功能。例如:
spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true
启用了服务发现功能,并将服务 ID 转换为小写
客户端发送请求到Spring Cloud Gateway,Gateway Handler Mapping确定请求与路由匹配,则会将请求交给Gateway Web Handler处理。
图片来源spring官网 https://cloud.spring.io/spring-cloud-gateway/reference/html/
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 Mappredicates; // 路由条件工厂类映射表 private final GatewayFilterHandlerFilter filterHandlerFilter; // 过滤器处理器 private final Map globalFilters; // 全局过滤器映射表 private final RouteDefinitionLocator routeDefinitionLocator; // 路由规则定位器 public RoutePredicateHandlerMapping(List predicates, GatewayFilterHandlerFilter filterHandlerFilter, List 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 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 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
在 RoutePredicateHandlerMapping 中,首先通过构造方法初始化了路由条件工厂类映射表、过滤器处理器、全局过滤器映射表和路由规则定位器。然后,实现了 AbstractHandlerMapping 中的 getHandlerInternal 方法。在 getHandlerInternal 方法中,首先获取所有路由规则,并遍历每个路由规则。对于每个路由规则,将其路由条件工厂类名称作为 key,从路由条件工厂类映射表中获取对应的路由条件工厂类,并使用路由条件工厂类创建路由条件。然后,判断当前请求是否满足路由条件,如果满足,则创建路由对象,并获取路由规则中的过滤器和全局过滤器。将这些过滤器组成过滤器链,并将过滤器链和默认的 Web 处理器一起作为参数创建过滤器 Web 处理器。最后,使用过滤器 Web 处理器和当前请求创建 DefaultWebHandlerAdapter 的实例,并返回路由转发处理器。
写到这儿其实我们只是了解了一个请求在路由到后台服务之前必须要要经过的几道工序,就如同我最开始从Spring 官网获得的工作原理图。
我们来回答最开始的那个问题。那么如果在不配置路由规则的Spring Cloud Gateway 服务中,网关是如何做的转发呢,这才是我们核心问题。
那就不得不说一个重要的核心的接口和实现类 位于spring-cloud-gateway-core-2.x.RELEASE 下。
RouteDefinitionLocator。其实里面就一个核心方法 getRouteDefinitions 。
DiscoveryClientRouteDefinitionLocator 是RouteDefinitionLocator 实现类。是 Spring Cloud Gateway 提供的一个基于服务发现的路由规则定位器,它可以自动将服务实例列表转换为路由规则,从而实现基于服务发现的路由配置。
为了方便大家理解,我在源码上添加了一些注释
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator { // 服务发现客户端 private final DiscoveryClient discoveryClient; // 路由规则转换器 private final RouteDefinitionLocator routeDefinitionLocator; // 服务过滤器 private final Predicatepredicate; // 默认使用所有服务实例 public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator) { this(discoveryClient, routeDefinitionLocator, instance -> true); // 默认使用所有服务实例 } // 第二个构造方法可以指定服务过滤器 public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator, Predicate predicate) { this.discoveryClient = discoveryClient; this.routeDefinitionLocator = routeDefinitionLocator; this.predicate = predicate; } @Override public Flux getRouteDefinitions() { return Flux.fromIterable(getServiceNames()) // 获取所有服务名称 .flatMap(this::getRoutes) // 遍历每个服务名称,获取该服务的路由规则 .flatMap(routeDefinitionLocator::getRouteDefinitions) // 转换路由规则 .doOnNext(route -> logger.debug("RouteDefinition matched: " + route)); // 打印日志 } private List getServiceNames() { return discoveryClient.getServices(); // 获取服务名称列表 } private Mono 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 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接口,用于获取路由规则列表。
好了今天的分享就到这儿,希望三分钟的阅读对你有所收获。我是冰点,下次再见。