gateway通常指的是网关, 这里所说的gateway指的是Spring Cloud Gateway(一下简称gateway), 在整个Spring Cloud体系中gateway的作用非常重要, 所有的请求都会经过gateway进行路由分发到后端具体的服务.
本篇文章从以下几点开始介绍:
什么是gateway呢其实官方文档已经给出了介绍, 我们来看下面这段内容:
This project provides a library for building an API Gateway on top of Spring MVC. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.
这里说到Gateway是在SpringMVC上构建的API网关库, 也就是说实际上gateway是在SpringMVC上层进行构建的一个API工具, 在这个工具里包含了请求转发、限流、请求拦截等功能, 这些功能是包装在gateway内部的Filter中.
其实这里说到的MVC指的应该是Spring的MVC编程模型和套路框架, 而不是传统的基于servlet的MVC, 因为在Spring2.0之后推出的gateway所使用的web框架已经更换成了reactive实现的WebFlux, 但本质上他们的框架原理和逻辑都是相似的, 只是对I/O的处理方式存在异同.
所以说本质上gateway这个服务是被嵌套在SpringMVC框架中的
既然gateway是基于SpringMVC的那我们就不得不看一下容器的选择以及初始化的阶段了, 作为一个高性能的网关容器的选型必然是Netty, 和传统的MVC一样, 在Spring初始化的阶段会去选择所使用的容器, 并且完成相应的参数配置.
具体操作步骤分如下:
Spring的初始化是通过ApplicationContext中的refresh进行的跟进代码可以看到具体所使用的方法
下面这段代码是实际上初始化Netty容器的地方
createWebServer() -> getWebServerFactoryBeanName()
这里初始化的实际上是web容器到目前位置和gateway没有任何关系, 下面开始介绍gateway的初始化过程.
gateway的初始化是在容器的启动过程中创建的, 本质上就是通过Spring帮助我们构建一个个Bean对象, 然后将这些功能模块拼装到SpringMVC中.
SpringBoot加载插件的方式是通过编写一个个start实现的, 所以如果想要找到gateway的AutoConfig配置方法只需要去他的factiries中看一下就可以.
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayHystrixCircuitBreakerAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\
org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration
上面提的配置文件中清晰地提到了org.springframework.cloud.gateway.config. GatewayClassPathWarningAutoConfiguration这个文件便是gateway初始化的配置类了
在这个启动类中包含了gateway模块和netty-client两个模块, 接下来对这两个模块分别介绍
gateway启动的过程其实就是构建对象然后将各个模块拼装到一起交给RoutePredicateHandlerMapping
1. create route definition and collect
2. collect resource and compose RouteDefinitionRouteLocator
3. init filter handler
4. compose handler mapping
5. add refresh event listener
6. add HttpHeaderFilter beans
7. init GlobalFilter beans
8. init Predicate Factory beans
9. init GatewayFilter Factory beans
/**
* collect all route definition to composite route definition
*/
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(
List<RouteDefinitionLocator> routeDefinitionLocators) {
return new CompositeRouteDefinitionLocator(
Flux.fromIterable(routeDefinitionLocators));
}
这个Bean收集了所有的RouteDefintionLoator并且伪装成了一个对象放到RoutePredicateHandlerMapping中, 这里包含了InMemoryRouteDefinitionRepository, PropertiesRouteDefinitionLocator以及我们自定义的获取方式.
/**
* init filter handler
*/
@Bean
public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
logger.info("--------web filter--------");
//output filter name
for (GlobalFilter x : globalFilters) {
String[] split = x.getClass()
.toString()
.split("\\.");
logger.info(split[split.length-1]);
}
logger.info("--------end--------");
return new FilteringWebHandler(globalFilters);
}
上面这段代码收集了所有GlobalFilter其中包含框架默认提供的也包含我们自定义的, 进入到new方法可以看到在初始化的时候会根据order的值来进行排序
private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
return filters.stream().map(filter -> {
GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
if (filter instanceof Ordered) {
int order = ((Ordered) filter).getOrder();
return new OrderedGatewayFilter(gatewayFilter, order);
}
return gatewayFilter;
}).collect(Collectors.toList());
}
这个方法将上面收集到的FilteringWebHandler以及RouteLocator组合到一起构成一个对象这个对象作为gateway模块的入口负责接收处理请求
/**
* compose handler mapping
*/
@Bean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(
FilteringWebHandler webHandler, RouteLocator routeLocator,
GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator,
globalCorsProperties, environment);
}
gateway初始化的时候有连个部分, 其一是上面所介绍的, 其二就是NttyClient的初始化, 这里对client的初始化不做过多的介绍, 我们需要关注的是其中一些参数的初始化
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {
protected final Log logger = LogFactory.getLog(getClass());
@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.httpserver.wiretap")
public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(
......
}
};
}
@Bean
@ConditionalOnMissingBean
public HttpClient gatewayHttpClient(HttpClientProperties properties {
......
HttpClient httpClient = HttpClient.create(connectionProvider)
......
}
}
在上文提到gateway实际上是作为SpringMVC中的一个功能模块, 而gateway的初始化结束后会创建一个RoutePredicateHandlerMapping对象, 这个对象实现了HandlerMapping在SpringWeb容器初始化时一个重要的Bean是HttpHandler, 但是初始化HttpHandler是并没有HandlerMapping相关的对象
@Bean
public HttpHandler httpHandler(ObjectProvider<WebFluxProperties> propsProvider) {
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
// 获取WebHandler相关的Bean
WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder(
context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context);
......
return httpHandler;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SGWByj0j-1597136664375)(https://tva1.sinaimg.cn/large/007S8ZIlly1gg2qe9931rj31e60u0wyb.jpg)]
观察 httpHandler可以发现实际上RoutePredicateHandlerMappingBean的对象实际上是被DispatcherHandler进行管理的而DispatcherHandler属于WebHandler
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
initStrategies(applicationContext);
}
protected void initStrategies(ApplicationContext context) {
Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
......
}
讲解了gateway类的初始化之后我们来说一下这其中存在的调用关系, 我先从最外层的spring容器说起
上图可以看出在gateway的工作流程中最外层的是RoutePredicateHandlerMapping实际上他是HandlerMapping的实现类可以看一下这个类的注释,
/**
* Interface to be implemented by objects that define a mapping between
* requests and handler objects.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 5.0
*/
public interface HandlerMapping {
......
}
可以看到这个接口是用来定义接受请求的类, 也就是说RoutePredicateHandlerMapping实际上是将gateway的模块都封装了起来, 然后将自己作为一个HandlerMapping注册到DispatcherHandler而这个类则是实现了WebHandler
/**
* Contract to handle a web request.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public interface WebHandler {
......
}
通过这段注释可以看出实际上DispatcherHandler是作为一个WebHandler交给HttpHandler的实现类去处理的
经过上述分析正印证了开头对gateway的定义, 作为SpringMVC中的一个模块存在
站长图很好地定义了HttpHandler、DispatcherHandler和RoutePredicateHandlerMapping的关系
这时整个gateway的模块已经安装到了DispatcherHandler中, gateway的初始化算是完成了, 下面开始看一下gateway是如何处理一个请求的.
下面这个图是处理连接事件的地方, 可以观察到实际存在4种事件类型
connect -> configure -> request_receive -> disconnecting
这里的handler实际上实在父类中定义的一个function, 我们跟进代码直接查看父类可以找到函数的定义
这个函数看起来非常亲切, 是的就是这个方法将netty定义的request以及response转变为了我们gateway中的request和response, 同时也屏蔽了response开辟内存空间的功能, 封装到了 NettyDataBufferFactory中(这里我们可以考虑一下内存释放的问题)
回到上一个类中, 继续看这个方法, 他是一个mono意味等待请求接受之后立即订阅, 而subscriber则是ops这个对象, 那我们来看一下这个对象是什么吧
可以发现这个对象实际上是继承自HttpOperations, 父类是ChannelOperations, 这个对象在netty中代表着一个channel, 也就是说代表着一次请求, 直接使用channel的worker线程来消费则可以说明如果在消费的过程中产生阻塞则会阻塞整个事件循环
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NYdznZtC-1597136664389)(https://tva1.sinaimg.cn/large/007S8ZIlly1gg2uami2buj31ke0lwdkt.jpg)]
这里有一个神奇的逻辑, 这里的filter是webflux的filter, 其实到现在还没有真正进入Gateway的逻辑
这里开始处理DispatcherHandler, 通过上一节讲的routmapping和filterhandler进行转换, 进入到gateway的filter处理逻辑中
对于gateway中的filter这里就不做过多的赘述了, 大家对filter都是非常的熟悉, 但是这里我会强调几个filter处理特殊情况的点
这里需要强调一下 这个是netty-client使用的filter
到这里 整个请求的流程就结束了
在写本篇文章的时候我发现gateway的版本从2.2.2升级到了2.2.4 就这小小小小的版本变化在处理body体的问题上都有很大的不同(不得不说这个框架是在不断进步的, 但是同时也告诉我们这个框架是有很多坑的, 对的我们就经常掉到坑里), 但是框架的本质是不会改变的, 框架的整体结构是不会改变的.
持续关注一个框架的变化你会发现作者对于框架演进的优化方向,而且每次版本更新之后满怀期待的去看一下曾经关注的点是否出现了变化,如果出现了变化会立刻想办法找到作者优化的思想和逻辑,其实这是一个挺有意思的过程,会潜移默化地影响你的编程逻辑.
本次的分享主要是和大家一起看一下最贴近我们的gateway对于一个请求的处理流程, 是想让我们在理解现在网关中的代码, 和解决目前存在的问题带来一个帮助, 后续我会继续研究gateway的框架, 跟进他的版本迭代, 在此基础上还要去深挖其背后的原理, 站在netty的基础上再来剖析一次gateway.