爲什麼寫?
就想看看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方法的時候建立連接。
一些常用條件註解總結
參考: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));
}
時序圖: