SpringGateway转发过程
为什么写?就想看看springgateway的限流咋做的?但是看着看着就想知道转发过程,然后就写了,总之:转发是通过重组请求头header、uri等信息建立netty客户端连接的访问过程。Lettuce相较于Jedis有哪些优缺点?Lettuce 和 Jedis 的定位都是Redis的client,所以他们当然可以直接连接redis server。Jedis在实现上是直接连接的redis serv
为什么写?
就想看看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));
}
时序图:
更多推荐
所有评论(0)