文章目录
一、前言
至此微服务网关系列文章已出:
- 【云原生&微服务>SCG网关篇一】为什么要有网关、生产环境如何选择网关
- 云原生&微服务>SCG网关篇二】生产上那些灰度发布方式
- 【云原生&微服务>SCG网关篇三】Spring Cloud Gateway是什么、详细使用案例
- 云原生&微服务>SCG网关篇四】Spring Cloud Gateway内置的11种PredicateFactory如何使用
- 【云原生&微服务>SCG网关篇五】Spring Cloud Gateway自定义PredicateFactory
- 【云原生&微服务>SCG网关篇六】Spring Cloud Gateway内置的18种Filter使用姿势
- 【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试
- 【云原生&微服务>SCG网关篇八】Spring Cloud Gateway三种自定义Filter、GlobalFilter的方式
- 【云原生&微服务>SCG网关篇九】Spring Cloud Gateway集成Nacos详细案例
- 【云原生&微服务>SCG网关篇十】Spring Cloud Gateway集成Actuator、Zipkin详细案例
- 【云原生&微服务>SCG网关篇十一】Spring Cloud Gateway解决跨域问题
- 【云原生&微服务>SCG网关篇十二】Spring Cloud Gateway集成Sentinel API实现多种限流方式
聊了以下问题:
- 为什么要有网关?网关的作用是什么?
- 网关的分类?
- 网关的技术选型?
- 使用网关时常用的灰度发布方式有哪些?
- Spring Cloud Gateway是什么?详细使用案例?
- Spring Cloud Gateway内置的11种PredicateFactory
- 如何自定义PredicateFactory?
- Spring Cloud Gateway内置的18种常用的Filter
- Spring Cloud Gateway基于内置Filter实现限流、熔断、重试
- Spring Cloud Gateway三种自定义Filter、GlobalFilter的方式
- Spring Cloud Gateway集成Nacos案例
- Spring Cloud Gateway集成Actuator、Zipkin案例
- Spring Cloud Gareway如何解决CORS跨域问题
- Spring Cloud Gateway集成Sentinel API实现限流
其中很多是Spring Cloud Gateway的使用,此篇文章开始进行Spring Cloud Gateway的源码分析。响应式编程是真的很难调试 / debug,大家注意看我代码流程截图中打的断点,不然自己F7、F8会进入到响应式编程的API中,很难走出来。关于响应式编程的介绍,后续博主有时间会专项出一个系列。
PS:SpringCloud版本信息:
<properties>
<spring-boot.version>2.4.2</spring-boot.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
二、Spring Cloud Gateway的由来
Spring Cloud Gateway 是Spring官方推出的一款API网关,其包含Spring5、SpringBoot2、Project Reactor,其中底层通信框架用的netty。Spring Cloud Gateway在推出的时候,Netflix公司已经推出了ZUUL,但是ZUUL有一个很致命的缺点:其通信方式是同步阻塞的,虽然后续升级出了异步非阻塞式的Zuul2;但是由于Spring Cloud Gateway已经推出了一段时间,并且用户反响还不错,而Zuul2自身也面临资料少、维护性较差等因素没有被广泛应用;
三个关键模块
在使用Spring Cloud Gateway的时候有三个核心模块:
Route:
- 路由是⽹关最基础的部分,路由信息由⼀个ID、⼀个目标URL、⼀组断⾔
Predicate
和⼀组Filter组成。如果断⾔路由为真,则说明请求的URL和配置匹配;也可以把路由理解为一条请求转发规则,看做是集URI、predicate、filter等属性的一个元数据类。
Predicate:
Predicate
作为路由的匹配条件,其是Java8函数式编程的一个方法,可以看做是满足什么条件的时候,route规则进行生效。
Filter:
- Filter是Spring Cloud Gateway最核心的模块;鉴权、安全、限流、熔断、逻辑执行、网络调用都是Filter来完成的,其中又细分为gateway filter和global filter,区别在于是具体一个route规则生效还是所有route规则都生效。
三者的关联:
三、Spring Cloud Gateway执行流程
上图是Spring Cloud Gateway官方文档给出的一个工作原理图,Spring Cloud Gateway 接收到请求后进行路由规则的匹配,然后交给web handler 进行处理,web handler 会执行一系列的filter逻辑。
更细粒度的执行流程
四、调试程序信息
调试程序采用博文(Spring Cloud Gateway是什么、详细使用案例)中的;另外需要对gateway-center
项目的application.yml配置文件做一些调整,如下:
server:
port: 9999
spring:
cloud:
gateway:
routes:
- id: ingredients-fallback
uri: http://127.0.0.1:9001
predicates:
- Path=/fallback
# 通过过滤器将地址重写为:/hello/sayParam
filters:
- SetPath=/hello/sayParam
- id: my_route
uri: http://127.0.0.1:9001
predicates:
# - Path=/**
- Path=/gateway/simple-service/**
filters:
- StripPrefix=2
# 自定义过滤器的名字,即:MyLogGatewayFilterFactory
- name: MyLog
args:
name: Saint_log_name
上述配置中涉及的内置Predicate和自定义filter参考博文:Spring Cloud Gateway内置的11种PredicateFactory如何使用、Spring Cloud Gateway三种自定义Filter、GlobalFilter的方式。
下面基于请求:http://127.0.0.1:9999/gateway/simple-service/hello/sayHello
,做Gateway处理请求的流程分析。
五、Gateway处理请求的流程分析
1、接收请求的入口
Springcloud Gateway采用 Netty Server
接收到客户端的请求;在接收请求时有一个关键类ReactorHttpHandlerAdapter
,ReactorHttpHandlerAdapter只做一件事:将netty的请求/响应 转换为HTTP的请求/响应 并交给http handler 执行后面的逻辑;
ReactorHttpHandlerAdapter实例化的时机
ReactorHttpHandlerAdapter
是在SpringBoot启动流程中初始化WebServer时实例化的,从AbstractApplicationContext#refresh()
方法开始流程如下:
2、HttpHandler开始解析请求
http handler(HttpWebHandlerAdapter
)首先将request 和 response转为一个ServerWebExchange(可以理解为网关的上下文),ServerWebExchange非常核心,它是各个filter之间参数流转的载体,其包含request、response、attributes(扩展字段);然后进行web filter链的执行。
从ReactorHttpHandlerAdapter#apply()
方法接收到请求之后的代码执行链路如下:
到这里进入到了HttpWebHandlerAdapter
类的handle()
方法,其负责组装网关的上下文(ServerWebExchange
),然后将其传递给后续的代理handle(ExceptionHandlingWebHandler
)处理;代码执行流程如下:
最终进入到FilteringWebHandler
中;
3、Web过滤器链WebFilterChain
FilteringWebHandler
组合了一个Web过滤器链WebFilterChain
;
public class DefaultWebFilterChain implements WebFilterChain {
private final List<WebFilter> allFilters;
private final WebHandler handler;
@Nullable
private final WebFilter currentFilter;
@Nullable
private final DefaultWebFilterChain chain;
}
Web过滤器链WebFilterChain
中包含一条filter链(WebFilter
集合),WebFilter主要用于实现横切、应用程序无关的需求,如安全性、超时等;由此可见Spring Cloud Gateway的过滤器设计是层层嵌套,扩展性很强。
就一个最简单的gateway程序而言,Web过滤器链WebFilterChain
中仅包含一个WebFilter(WeightCalculatorWebFilter
),其用于计算权重;具体代码执行流程如下:
由于ServerWebExchange
中默认没有权重信息,所以WeightCalculatorWebFilter
中什么也不做,直接进入Web过滤器链WebFilterChain
的下一个WebFilter
,由于Web过滤器链WebFilterChain
中仅有一个WeightCalculatorWebFilter
,所以下一个WebFilter是null。
在Spring Cloud Gateway中所有的WebFilter过滤操作都体现在WebFilterChain
接口的实现类DefaultWebFilterChain
#filter()方法,所以进到如下代码中:
三元表达式的逻辑为:如果WebFilterChain中的当前WebFilter和组合的webFilterChain都不为空,则执行当前WebFilter,否则进入DispatcherHandler#handle()方法处理请求;
此时,currentFilter 和 chain均已为空,接下来会直接进入到DispatcherHandler中;
4、真正处理请求的入口DispatcherHandler
Spring Cloud gateway处理请求的入口类为DispatcherHandler,其负责请求的分发;DispatcherHandler可以和 Spring Mvc的 DispacherServlet,Spring MVC执行流程参考博文:图文源码分析Spring MVC请求执行流程。
DispatcherHandler#handle()方法逻辑如下:
- DispatcherHandler 首先判断持有的handerMappings是否为空,为空则调用 createNotFoundError方法,返回 No matching handler信息;
- 接着有序遍历 handerMappings并调用 mapping的getHandler()方法返回mapping对应的handler。
DispatcherHandler持有四个HandlerMapping:RouterFunctionMapping、RequestMappingHandlerMapping、RoutePredicateHandlerMapping、SimpleUrlHandlerMapping。
下面接着来看这四个HandlerMapping分别对请求做了哪些处理?
这四个HandlerMapping都实现接口AbstractHandlerMapping
,所以调用其getHandler()方法都会进入到AbstractHandlerMapping#getHandler()
方法,进而调用每个HandlerMapping自己实现的getHandlerInternal()
方法:
1)RouterFunctionMapping默认啥也不做
请求走到RouterFunctionMapping的getHandlerInternal()方法,代码执行流程如下:
默认RouterFunction
为null,直接返回 Mono.empty();
2)RequestMappingHandlerMapping默认啥也不做
RequestMappingHandlerMapping的类图如下:
由于RequestMappingHandlerMapping中没有重写getHandlerInternal()方法,所以请求达到其父类RequestMappingInfoHandlerMapping
的getHandlerInternal()方法,代码执行流程如下:
这里本质上和RouterFunctionMapping一样都是直接返回 Mono.empty();
3)RoutePredicateHandlerMapping获取Route路由规则
RoutePredicateHandlerMapping是做路由匹配的核心类,部分代码执行流程如下:
RoutePredicateHandlerMapping#getHandlerInternal()方法中首先会调用lookupRoute()
方法获取到所有的Route,代码执行流程如下:
方法中调用routeLocator.getRoutes()获取到所有的Route,然后针对请求遍历每个Route做Predicate路由规则匹配,找到与请求匹配的路由;
根据示例,这里会找到两个Route路由:
1> 不符合谓词Predicate的路由
请求访问的请求路径是/gateway/simple-service/hello/sayHello
;而Route(ingredients-fallback
)做匹配的谓词工厂是PathRoutePredicateFactory
,匹配路径为:/fallback
;
下面为具体的代码执行流程:
请求路径/gateway/simple-service/hello/sayHello
和Predicate路径/fallback
不匹配,返回false,表示路由的谓词不匹配。
2> 符合谓词Predicate的路由
请求访问的请求路径是/gateway/simple-service/hello/sayHello
;Route(my_route
)做匹配的谓词工厂是PathRoutePredicateFactory
,匹配路径为:/gateway/simple-service/**
;
下面为具体的代码执行流程:
请求路径/gateway/simple-service/hello/sayHello
和Predicate路径/gateway/simple-service/**
匹配,返回true,表示路由的谓词匹配。
3>路由匹配完之后返回FilteringWebHandler
4)SimpleUrlHandlerMapping啥也不做
由于RoutePredicateHandlerMapping中已经获取到了FilteringWebHandler,所以不会走进SimpleUrlHandlerMapping,而是至今后续的FilterWebHandler的执行;
5、核心过滤器链FilterWebHandler执行
1)从DispatcherHandler到FilterWebHandler的流转
找到路由规则后,将执行过滤器链的核心类FilteringWebHandler
返回到DispatcherHandler中,代码执行流程如下:
DispatcherHandler的invokHandler()方法中首先会获取到四个HandlerAdapter,分别为:WebSocketHandlerAdapter、RequestMappingHandlerAdapter、handlerFunctionAdapter、SimpleHandlerAdapter;
然后遍历这个四个HandlerAdapter,判断其是否支持FilterWebHandler
的执行;最后仅有SimpleHandlerAdapter
支持FilterWebHandler
的执行:
SimplerHandlerAdapter#handle()
方法中直接调用的FilteringWebHandler#handle()
方法;
这里之所以可以直接把FilteringWebHandler强转为WebHandler,是因为FilteringWebHandler实现自WebHandler接口;
2)FilteringWebHandler的工作
进入到FilteringWebHandler#handle()
方法看看都做了些什么;
FilteringWebHandler中做的事很简单,四件事:
1>从ServerWebExchange中首先获取路由Route,然后获取Route对应的局部过滤器Filter;
- 这里的Route是在
RoutePredicateHandlerMapping
类中获取到可用的Route之后,添加到ServerWebExchange的属性中的;
2> 获取所有的全局过滤器GlobalFilter;
3> 对所有的过滤器进行排序;
- 排序规则可以参考博文:条件装配时多个Condition执行的顺序是什么样的?
4> 最后执行过滤器链中的所有过滤器;
看一下过滤器链中有哪些过滤器:
一共11个过滤器,针对Route(my_route
),本文使用了两个Filter(Gateway内嵌的StripePrefix
和我们自定义的MyLog
);其余9个全是Gateway自带的GlobalFilter。
以自定义的MyLog
为例,来看一下Filter是如何执行的?
看到这,博主想了一个问题,gateway是何时 怎么将请求转发到具体的服务的?
6、请求转发到特定服务
上面我们看到过滤器链DefaultGatewayFilterChain
中有11个Filter,其中有9个是GlobalFilter:
1)请求负载均衡?
我们可以注意到有一个过滤器叫NoLoadBalancerClientFilter
(GatewayNoLoadBalancerClientAutoConfiguration的内部类),从它的命名来看就感觉它和负载均衡可能有点关系,进入它的代码执行逻辑验证一下:
看到最后这个lb
字符串,大家应该感觉很熟悉了,在我们使用服务注册中心,Route中配置的uri往往是lb://
为前缀,例如:
由于这里我们的URI是直接指定的IP:post地址,所以不会走负载均衡;关于使用了服务注册中心之后如何做负载均衡,博主后面再出一篇文章。
2)请求执行 <–NettyRoutingFilter
当请求scheme
是以http
或https
为scheme时(通俗了讲就是经过前面一些Filter处理之后,gatewayRequestUrl是http://ip:port/xxx/xx
或 https://ip:port/xxx/xx
),NettyRoutingFilter负责将请求通过 Netty Proxy(说白了就是一个HttpClient)打到指定的HTTP服务上;
3)请求转发 <–ForwardRoutingFilter
当需要对请求进行forward时,请求转发由全局过滤器ForwardRoutingFilter来执行;ForwardRoutingFilter直接复用了Spring MVC的能力,将请求提交给dispatcherHandler进行处理,dispatcherHandler根据path前缀找到所需要的目标处理器执行逻辑;
7、响应回写
响应回由全局过滤器NettyWriteResponseFilter来执行,但是我们可以注意到执行器链中NettyWriteResponseFilter是排在最前面(除Cache相关Filter之外);咦,按道理来说这种处理响应的过滤器类应该放在比较靠后的位置才对啊!此处的设计就比较有意思,我们可以看到chain.filter(exchange).then()
这段逻辑,意思就是执行到我当前Filter的时候直接通过chain.filter()
执行后一个Filter,等后面的过滤器都执行完后再返回执行then()
中逻辑。
这种行为控制的方式还是很值得学习的,往简单了想,这里和Spring的BeanPostProcessor
类的postProcessBeforeInitialization()和postProcessAfterInitialization()类似,强调的是当前逻辑处理之前 / 处理完之后,再做什么事。
总结
当请求过来时:首先由中央处理器DispatcherHandler进行分发,通过它找到对应的RoutePredicateHandlerMapping,进而找到请求匹配的Route、获取到FilteringWebHandler;然后再由FilteringWebHandler组合好过滤器链,通过滤器链处理请求,将请求引导到真实的服务器上进行处理;
总感觉Spring Cloud Gateway更像是一个过滤器链执行框架;因为实际的请求转发 / 响应回写都是在过滤器中做的;此外SpringCloud Gateway内置了很多过滤器,有一些是可以不用的,如果有个卸载过滤器的功能就很哇塞了;当然并不是每个人都可以去随便增减Spring Cloud Gateway内置过滤器的,需要有一定的知识沉淀,否则鬼知道踩什么坑呢。