SpringGateway轉發過程

爲什麼寫?

就想看看springgateway的限流咋做的?但是看着看着就想知道轉發過程,然後就寫了,總之:轉發是通過重組請求頭header、uri等信息建立netty客戶端連接的訪問過程。

Lettuce相較於Jedis有哪些優缺點?

Lettuce 和 Jedis 的定位都是Redis的client,所以他們當然可以直接連接redis server。

Jedis在實現上是直接連接的redis server,如果在多線程環境下是非線程安全的,這個時候只有使用連接池,爲每個Jedis實例增加物理連接

Lettuce的連接是基於Netty的,連接實例(StatefulRedisConnection)可以在多個線程間併發訪問,應爲StatefulRedisConnection是線程安全的,所以一個連接實例(StatefulRedisConnection)就可以滿足多線程環境下的併發訪問,當然這個也是可伸縮的設計,一個連接實例不夠的情況也可以按需增加連接實例。

SpringBoot2.0後之前的jedis已經改成Lettuce了

參見:org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration,

生效註解:@ConditionalOnClass(RedisClient.class)

RedisURI採用靜態內部類Builder進行建造者模式構建ip端口等連接參數,builder.build()實例化RedisURI,之後使用RedisClient.create(redisUri)進行RedisClient客戶端實例化,但還未建立連接,之後在RedisClient調用connect方法的時候建立連接。

 lettuce超級鏈接

一些常用條件註解總結

參考:https://fangjian0423.github.io/2017/05/16/springboot-condition-annotation/

條件註解 對應的Condition處理類 處理邏輯
@ConditionalOnBean OnBeanCondition Spring容器中是否存在對應的實例。可以通過實例的類型、類名、註解、暱稱去容器中查找(可以配置從當前容器中查找或者父容器中查找或者兩者一起查找)這些屬性都是數組,通過”與”的關係進行查找
@ConditionalOnClass OnClassCondition 類加載器中是否存在對應的類。可以通過Class指定(value屬性)或者Class的全名指定(name屬性)。如果是多個類或者多個類名的話,關係是”與”關係,也就是說這些類或者類名都必須同時在類加載器中存在
@ConditionalOnExpression OnExpressionCondition 判斷SpEL 表達式是否成立
@ConditionalOnJava OnJavaCondition 指定Java版本是否符合要求。內部有2個屬性value和range。value表示一個枚舉的Java版本,range表示比這個老或者新於等於指定的Java版本(默認是新於等於)。內部會基於某些jdk版本特有的類去類加載器中查詢,比如如果是jdk9,類加載器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,類加載器中需要存在java.util.function.Function;如果是jdk7,類加載器中需要存在java.nio.file.Files;如果是jdk6,類加載器中需要存在java.util.ServiceLoader
@ConditionalOnMissingBean OnBeanCondition Spring容器中是否缺少對應的實例。可以通過實例的類型、類名、註解、暱稱去容器中查找(可以配置從當前容器中查找或者父容器中查找或者兩者一起查找)這些屬性都是數組,通過”與”的關係進行查找。還多了2個屬性ignored(類名)和ignoredType(類名),匹配的過程中會忽略這些bean
@ConditionalOnMissingClass OnClassCondition 跟ConditionalOnClass的處理邏輯一樣,只是條件相反,在類加載器中不存在對應的類
@ConditionalOnNotWebApplication OnWebApplicationCondition 應用程序是否是非Web程序,沒有提供屬性,只是一個標識。會從判斷Web程序特有的類是否存在,環境是否是Servlet環境,容器是否是Web容器等
@ConditionalOnProperty OnPropertyCondition 應用環境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing屬性。prefix表示屬性名的前綴,name是屬性名,havingValue是具體的屬性值,matchIfMissing是個boolean值,如果屬性不存在,這個matchIfMissing爲true的話,會繼續驗證下去,否則屬性不存在的話直接就相當於匹配不成功
@ConditionalOnResource OnResourceCondition 是否存在指定的資源文件。只有一個屬性resources,是個String數組。會從類加載器中去查詢對應的資源文件是否存在
@ConditionalOnSingleCandidate OnBeanCondition Spring容器中是否存在且只存在一個對應的實例。只有3個屬性value、type、search。跟ConditionalOnBean中的這3種屬性值意義一樣
@ConditionalOnWebApplication OnWebApplicationCondition 應用程序是否是Web程序,沒有提供屬性,只是一個標識。會從判斷Web程序特有的類是否存在,環境是否是Servlet環境,容器是否是Web容器等
例子 例子意義
@ConditionalOnBean(javax.sql.DataSource.class) Spring容器或者所有父容器中需要存在至少一個javax.sql.DataSource類的實例
@ConditionalOnClass
({ Configuration.class,
FreeMarkerConfigurationFactory.class })
類加載器中必須存在Configuration和FreeMarkerConfigurationFactory這兩個類
@ConditionalOnExpression
(“‘${server.host}’==’localhost’”)
server.host配置項的值需要是localhost
ConditionalOnJava(JavaVersion.EIGHT) Java版本至少是8
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) Spring當前容器中不存在ErrorController類型的bean
@ConditionalOnMissingClass
(“GenericObjectPool”)
類加載器中不能存在GenericObjectPool這個類
@ConditionalOnNotWebApplication 必須在非Web應用下才會生效
@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) 應用程序的環境中必須有spring.aop.auto這項配置,且它的值是true或者環境中不存在spring.aop.auto配置(matchIfMissing爲true)
@ConditionalOnResource
(resources=”mybatis.xml”)
類加載路徑中必須存在mybatis.xml文件
@ConditionalOnSingleCandidate
(PlatformTransactionManager.class)
Spring當前或父容器中必須存在PlatformTransactionManager這個類型的實例,且只有一個實例
@ConditionalOnWebApplication 必須在Web應用下才會生效

路由分析


​​路由信息:

GatewayProperties成員變量private List<RouteDefinition> routes = new ArrayList<>();存儲配置文件的基本路由信息,配置類GatewayAutoConfiguration實例化CachingRouteLocator,將路由信息routeLocators注入到CompositeRouteLocator中,CompositeRouteLocator構造參數是Fluw的Iterable創建的數據流,是反應是編程創建的一種,這裏可以想到的是方便遍歷路由信息,反應式編程常用操作。需要說明的是CachingRouteLocator是一個RefreshRoutesEvent的事件監聽器。

@Bean
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
		return new CachingRouteLocator(
				new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
	}

在Applicationcontext的refresh最後一步finishRefresh()過程中,調用監聽器(觀察者模式),而後觸發了RefreshRoutesEvent事件,調用CachingRouteLocator的onApplicationEvent方法轉換路由信息。保存到成員變量Flux<Route> routes中。

HadlerMapping信息:

把路由RouteLocator注入到HadlerMapping中,目的值當客戶端訪問的時候,通過路由信息匹配lookupRoute

@Bean
	public RoutePredicateHandlerMapping routePredicateHandlerMapping(
			FilteringWebHandler webHandler, RouteLocator routeLocator,
			GlobalCorsProperties globalCorsProperties, Environment environment) {
		return new RoutePredicateHandlerMapping(webHandler, routeLocator,
				globalCorsProperties, environment);
	}

訪問過程:

訪問的第一站是DispatcherHandler類,執行其中的getHandler獲取合適的執行器執行invokeHandler方法

@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		if (this.handlerMappings == null) {
			return createNotFoundError();
		}
		return Flux.fromIterable(this.handlerMappings)
				.concatMap(mapping -> mapping.getHandler(exchange))
				.next()
				.switchIfEmpty(createNotFoundError())
				.flatMap(handler -> invokeHandler(exchange, handler))
				.flatMap(result -> handleResult(exchange, result));
	}
getHandler匹配路由信息
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		return this.routeLocator.getRoutes()
				// individually filter routes so that filterWhen error delaying is not a
				// problem
				.concatMap(route -> Mono.just(route).filterWhen(r -> {
					// add the current route we are testing
					exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
					return r.getPredicate().apply(exchange);
				})
						// instead of immediately stopping main flux due to error, log and
						// swallow it
						.doOnError(e -> logger.error(
								"Error applying predicate for route: " + route.getId(),
								e))
						.onErrorResume(e -> Mono.empty()))
				// .defaultIfEmpty() put a static Route not found
				// or .switchIfEmpty()
				// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
				.next()
				// TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("Route matched: " + route.getId());
					}
					validateRoute(route, exchange);
					return route;
				});

	}
限流過濾器:


轉發屬性設置RouteToRequestUrlFilter,將執行exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
把轉發的url設置的exchange屬性裏邊,目的是執行到轉發過濾器的時候,取出來重新賦值到request url。
@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		if (route == null) {
			return chain.filter(exchange);
		}
		log.trace("RouteToRequestUrlFilter start");
		URI uri = exchange.getRequest().getURI();
		boolean encoded = containsEncodedParts(uri);
		URI routeUri = route.getUri();

		// 省略。。。

		URI mergedUrl = UriComponentsBuilder.fromUri(uri)
				// .uri(routeUri)
				.scheme(routeUri.getScheme()).host(routeUri.getHost())
				.port(routeUri.getPort()).build(encoded).toUri();
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
		return chain.filter(exchange);
	}
轉發路由NettyRoutingFilter:
@Override
	@SuppressWarnings("Duplicates")
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

		// 省略。。。。
		final String url = requestUrl.toASCIIString();

		HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

		final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
		filtered.forEach(httpHeaders::set);
 
		Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
				.headers(headers -> {
					headers.add(httpHeaders);
					// Will either be set below, or later by Netty
					headers.remove(HttpHeaders.HOST);
					if (preserveHost) {
						String host = request.getHeaders().getFirst(HttpHeaders.HOST);
						headers.add(HttpHeaders.HOST, host);
					}
				}).request(method)
                    // 重新設置uri
                .uri(url).send((req, nettyOutbound) -> {
					// 省略。。。。
		return responseFlux.then(chain.filter(exchange));
	}


時序圖:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章