SpringCloud gateway原理分析

前置说明

本文源码基于 springcloud Finchley 版本. 以下分析仅代表个人的理解, 如有错误, 欢迎探讨.

使用说明

以spring一贯的作风, 用户使用的模式都是约定俗成的, 也就是引入spring-cloud-starter-gateway 依赖, 然后就可以愉快的自动配置了. 但是这里有一个要说的点, springcloud gateway是只能使用在 webflux上面, 也就是说如果引入的springmvc依赖, 那么需要修改,不然只能使用zuul了.

   <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
   </dependency>

接下来就是根据自己的需求配置一些 路由的策略或者条件


spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: false  #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
          lowerCaseServiceId: true   #是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了),比如以/service-hi/*的请求路径被路由转发到服务名为service-hi的服务上。
          filters:
            - StripPrefix=1
      routes:
        - id: client2
          uri: lb://client2
          predicates:
            - Path=/client2/**
          filters:
            - StripPrefix=1


原理介绍

配置的加载

gateway的自动装配类主要在GatewayAutoConfiguration, 其配置了GatewayProperties, FilteringWebHandler, RouteLocator, RouteDefinitionLocator, 以及过滤器工厂和路由工厂等bean的配置

然后, 我们在配置文件中的路由配置的会被自动加载到 GatewayProperties, 然后在转换为RouteDefinition 存储在 GatewayProperties#routes

下面的图是调试中一个RouteDefinition , 其 主要包含了 predicate 和 filters
在这里插入图片描述

路由的获取

首先我们的入口在 org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler .

AbstractHandlerMapping 这个类我想应该大家看着很熟, 因为它很像springmvc 里面的 handlerMapping. 我认为这是 Spring团队想要webflux 和 springmvc 以相同的结构, 这样大家可以没有太大的学习成本来上手.

这边接下来会走到 org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal, 这个类时gateway 实现的 AbstractHandlerMapping 来用于实现网关路由功能

@Override
	protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
		if (managmentPort != null && exchange.getRequest().getURI().getPort() == managmentPort.intValue()) {
			return Mono.empty();
		}
		exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
		return lookupRoute(exchange) //超找所有的符合条件的路由
				.flatMap((Function<Route, Mono<?>>) r -> {
					exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
					exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
					return Mono.just(webHandler);// 返回要执行的过滤器执行器
				}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
					exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
					if (logger.isTraceEnabled()) {
						logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
					}
				})));
	}



protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		return this.routeLocator
				.getRoutes() // 从路由管理器中获取所有的路由
				// 转换和连接的操作符
				.concatMap(route -> Mono
						.just(route)
						.filterWhen(r -> { //过滤符合条件的route
							exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
							return r.getPredicate().apply(exchange); //判断路由是否符合条件
						})
						.doOnError(e -> logger.error("Error applying predicate for route: "+route.getId(), e))
						.onErrorResume(e -> Mono.empty())
				)
				.next() //返回第一个
				.map(route -> {
					validateRoute(route, exchange);
					return route;
				});
	}

上面看到, 主要是通过RouteLocator.getRoutes 的方法获取所有的路由, 默认的话是通过RouteDefinitionRouteLocator ,也就是通过RouteDefinition 的方式进行配置的路由. 当然我们也可以通过自定义RouteLocator 进行路由的提供, 然后通过CompositeRouteLocator 方式容器会进行自己的组装.

下面看下 org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getRoutes

@Override
	public Flux<Route> getRoutes() {
		return this.routeDefinitionLocator.getRouteDefinitions() // 获取配置和持久化的路由
				.map(this::convertToRoute)
				//TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("RouteDefinition matched: " + route.getId());
					}
					return route;
				});
	}


路由的话前面也提到了就是可以配置在配置文件中, 也就是 PropertiesRouteDefinitionLocator , 当然spring也提供了其他的配置方式, 比如 InMemoryRouteDefinitionRepository的方法, 这个方式可以通过 REST接口进行动态的添加了路由, 也可以自定义持久化方式, 进行数据库的存储等.


public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
	// 可以看到只是存储在map中
	private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());

	@Override
	public Mono<Void> save(Mono<RouteDefinition> route) {
		return route.flatMap( r -> {
			routes.put(r.getId(), r);
			return Mono.empty();
		});
	}

	@Override
	public Mono<Void> delete(Mono<String> routeId) {
		return routeId.flatMap(id -> {
			if (routes.containsKey(id)) {
				routes.remove(id);
				return Mono.empty();
			}
			return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: "+routeId)));
		});
	}

	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		return Flux.fromIterable(routes.values());
	}
}

路由对象的转换RouteDefinition -> Route


private Route convertToRoute(RouteDefinition routeDefinition) {
		AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition); //  获取路由判断器
		List<GatewayFilter> gatewayFilters = getFilters(routeDefinition); // 获取该路由要的过滤器

		return Route.async(routeDefinition)
				.asyncPredicate(predicate)
				.replaceFilters(gatewayFilters)
				.build();
	}

这里的话简单说一下, 就是配置文件中的

		predicates:
            - Path=/client2/**  这里的key =value , 对应了一个 RoutePredicateFactory
          filters:
            - StripPrefix=1  这里的key =value, 对应了一个 GatewayFilterFactory

按照我的配置文件写的 route包含的 predicateFactory 就是PathRoutePredicateFactory, filterFactory就是StripPrefixGatewayFilterFactory, 通过这两个工厂会返回真正的 predicate 和 filter.

流程的执行

上面的话已经讲到了请求进来根据URL来查找对应的handler, 所以接下来就是判断路由以及执行过滤器的逻辑.

Predicate的执行逻辑前面有提到,就是 org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute 中的这一行

return r.getPredicate().apply(exchange); //判断路由是否符合条件

这里可以看下我配置的predicate

@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		synchronized (this.pathPatternParser) {
			pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchOptionalTrailingSeparator());
			config.pathPattern = this.pathPatternParser.parse(config.pattern);
		}
		return exchange -> {
			// 解析路径
			PathContainer path = parsePath(exchange.getRequest().getURI().getPath());
			//路径匹配
			boolean match = config.pathPattern.matches(path);
			traceMatch("Pattern", config.pathPattern.getPatternString(), path, match);
			if (match) {
				PathMatchInfo uriTemplateVariables = config.pathPattern.matchAndExtract(path);
				exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
				return true;
			} else {
				return false;
			}
		};
	}


接下来看下 filter的执行, 也就是webHandler的执行过程
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle

public Mono<Void> handle(ServerWebExchange exchange) {
		Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
		List<GatewayFilter> gatewayFilters = route.getFilters(); // 获取该路由的过滤器
		List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
		combined.addAll(gatewayFilters);  // 结合全局的过滤器
		AnnotationAwareOrderComparator.sort(combined);
		if (logger.isDebugEnabled()) {
			logger.debug("Sorted gatewayFilterFactories: "+ combined);
		}
		// 构建过滤器链 以及执行
		return new DefaultGatewayFilterChain(combined).filter(exchange);
	}

可以看下我这边配置 ScriptFilter

@Override
	public GatewayFilter apply(Config config) {
		return (exchange, chain) ->  {
			ServerHttpRequest request = exchange.getRequest();
			addOriginalRequestUrl(exchange, request.getURI());
			String path = request.getURI().getRawPath();
			String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
					.skip(config.parts).collect(Collectors.joining("/")); //这里通过skip 跳过指定的不需要的 uri段
			ServerHttpRequest newRequest = request.mutate()
					.path(newPath)
					.build();

			exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());

			return chain.filter(exchange.mutate().request(newRequest).build());
		};
	}

下面是进行负载均衡的过滤器LoadBalancerClientFilter

@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
		// 这里就是我配置文件中    uri: lb://client2
		if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
			return chain.filter(exchange);
		}
		//preserve the original url
		addOriginalRequestUrl(exchange, url);
		// 根据负载均衡算法获取一个真正的远程服务器
		final ServiceInstance instance = choose(exchange);

		if (instance == null) {
			throw new NotFoundException("Unable to find instance for " + url.getHost());
		}

		URI uri = exchange.getRequest().getURI();

		String overrideScheme = null;
		if (schemePrefix != null) {
			overrideScheme = url.getScheme();
		}
		// 解析服务为具体的地址
		URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);

		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
		return chain.filter(exchange);
	}


总结

主要的知识点

  1. gateway的配置
  2. 路由的配置转换为routeDefinition
  3. 获取请求对应的路由规则, 将RouteDefinition转换为Route
  4. 执行Predicate判断是否符合路由, 以及执行相关的过滤(全局过滤器以及路由过滤器)
  5. 负载均衡过滤器负责将请求中的serviceId转换为具体的服务实例Ip
  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值