Spring Cloud Gateway-路由转发
Spring Cloud Gateway 支持三种类型的路由:静态路由、动态路由和自动路由。
本文介绍关于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来获取服务实例列表,并将这些服务实例转换为路由规则。
在 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 Cloud Gateway 是一款基于 Spring Framework 和 Spring Boot 的网关框架,它提供了统一的路由转发、负载均衡、请求过滤和请求转换等功能。在 Spring Cloud Gateway 中,路由转发是其中最核心的功能之一。下面是 Spring Cloud Gateway 路由转发的原理和源码解析。
路由转发原理
1.Spring Cloud Gateway 的路由转发基于 Netty 和 Reactor 实现。当一个请求到达 Spring Cloud Gateway 时,它会首先经过一系列过滤器的处理,然后根据路由规则将请求转发到正确的目标地址。
2.路由规则由路由配置组件管理,它可以通过多种方式来创建,例如基于配置文件的路由配置、基于 Java代码的路由配置、基于服务发现的路由配置等。每个路由规则包含一个路由条件和一个目标 URI,当一个请求满足路由条件时,它就会被转发到目标URI。
3.路由条件由路由规则的路由条件工厂类创建,例如
PathRoutePredicateFactory、HeaderRoutePredicateFactory、MethodRoutePredicateFactory等。它们可以根据请求的路径、请求头、请求方法等条件来判断一个请求是否满足路由条件。
4.目标 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 的实例,并返回路由转发处理器。
问题核心
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; } }
1.DiscoveryClientRouteDefinitionLocator 类的主要作用是从服务注册中心获取服务信息并将其转换为路由规则。它实现了 RouteDefinitionLocator 接口,用于获取路由规则列表。具体来说,它通过 DiscoveryClient 类获取所有服务名称,遍历每个服务名称,再通过 DiscoveryClient 类获取该服务的所有实例,最后将实例信息转换为路由规则。
2.getRouteDefinitions 方法是 DiscoveryClientRouteDefinitionLocator类的核心方法,用于获取所有的路由规则。它通过 Flux.fromIterable() 获取所有服务名称,然后通过 flatMap()方法遍历每个服务名称,获取该服务的路由规则。获取路由规则的方法是 getRoutes(),该方法通过 Mono.just()创建一个新的 RouteDefinition 对象,然后通过 Flux.fromIterable() 获取该服务的所有实例,再通过filter() 方法过滤服务实例,接着调用getInstanceRoute方法将服务实例转换为路由规则,最后通过reduce() 方法将所有路由规则合并成一个RouteDefinition 对象。在合并路由规则时,会调 mergeRouteDefinitions 方法实现合并过滤器和谓词的操作。
3.getServiceNames获取所有服务名称,它通过 DiscoveryClient 类的 getServices() 方法实现。
4.getInstances() 获取指定服务的所有实例,它通过 DiscoveryClient 类的 getInstances() 方法实现。
5.getInstanceRoute() 方法用于将服务实例转换为路由规则,它创建一个新的 RouteDefinition对象,将服务名称作为路由规则的 ID,将服务实例的 URI 作为路由规则的 URI,并返回该路由规则对象。
6.mergeRouteDefinitions方法用于合并路由规则,它将两个路由规则对象的过滤器和谓词合并到一个路由规则对象中,并返回该路由规则对象。