基于
spring-cloud-netflix-zuul-2.2.5.RELEASE.jar
,zuul版本1.3.1。
0. 目录
1. 前言
本文注定发表即过时,毕竟对于极致追求性能的网关来说,外有 spring-cloud-starter-gateway,内有新版本的zuul 2.x,所以本文主要作为过往相关研究的阶段性总结,直接一勺烩,不再做逐一概念解读,骗篇幅了。
2. SpringBoot之AutoConfig
既然是基于 spring-cloud-netflix-zuul 进行解读,那 SpringBoot源码解析之AutoConfiguration 就是绕不过去的了。
spring-cloud-netflix-zuul 下的 META-INF/spring.factories 文件中引入了ZuulServerAutoConfiguration
和 ZuulProxyAutoConfiguration
两个自动配置类。
ZuulProxyAutoConfiguration
为ZuulServerAutoConfiguration
的子类。可以理解为ZuulProxyAutoConfiguration
是ZuulServerAutoConfiguration
的升级版。ZuulProxyAutoConfiguration
是否生效取决于ZuulProxyMarkerConfiguration.Marker
实例存在于容器中。而向容器中注入Marker实例的操作位于ZuulProxyMarkerConfiguration
这个配置类中。这个配置类被容器扫描到的操作则是在注解@EnableZuulProxy
中完成的。这个落地同样适用于ZuulServerAutoConfiguration
的启用。最终形成的对应关系:
a.ZuulProxyAutoConfiguration + @EnableZuulProxy + ZuulProxyMarkerConfiguration
b.ZuulServerAutoConfiguration + @EnableZuulServer + ZuulServerMarkerConfiguration
- 所以,如果你想要启用高配版
ZuulProxyAutoConfiguration
,请在项目中使用注解@EnableZuulProxy
,否则使用@EnableZuulServer
。
作为子类,ZuulProxyAutoConfiguration
天然继承了父类ZuulServerAutoConfiguration
中所定义的Bean,所以接下来我们就以ZuulServerAutoConfiguration
为例,进行相关讲解。
2.1 ZuulProxyAutoConfiguration
引入的典型Bean
Bean | 源自 | 备注 |
---|---|---|
HasFeatures | ZuulProxyAutoConfiguration 。覆盖自ZuulServerAutoConfiguration 。 | SpringBoot提供的 actuator特性,对应于 spring-cloud-commons 中的 FeaturesEndpoint。 |
DiscoveryClientRouteLocator | ZuulProxyAutoConfiguration | 基于服务发现实现的RouteLocator 实现类 |
ServiceRouteMapper | ZuulProxyAutoConfiguration | 1. 为 service Id 和 route 之间建立一个转换。默认实现为两者相同的SimpleServiceRouteMapper 。2. 应用位置 DiscoveryClientRouteLocator.locateRoutes() |
ProxyRequestHelper | ZuulProxyAutoConfiguration | 辅助类,提供各类工具方法。存在子类TraceProxyRequestHelper |
PreDecorationFilter / RibbonRoutingFilter / SimpleHostRoutingFilter | ZuulProxyAutoConfiguration | route相关的ZuulFilter,之后进行专门的讲解。 |
ZuulController + ZuulHandlerMapping | ZuulServerAutoConfiguration | 1. 基于SpringMVC的HandlerMapping 扩展,将Zuul处理流程集成到SpringMVC主体处理流程中。2. 正是这两的配合,将 用户请求url 与 自己配置的处理filter 实际联系上的。 |
ZuulRefreshListener | ZuulServerAutoConfiguration | 响应事件,触发ZuulHandlerMapping 的 handler重新注册逻辑 |
ZuulServlet/ ZuulServletFilter | ZuulServerAutoConfiguration | 1. 根据配置zuul.use-filter=true/false 二选一,默认为ZuulServlet。2. ZuulServlet默认响应路径为 http://ip:port/zuul/{zuul service name}/yyy/zzz (等同于访问http://ip:port/{zuul service name}/yyy/zzz 路径,请求逻辑也会进行zuul处理流程)3. ZuulServlet默认将和DispatcherServlet平级。 |
ServletDetectionFilter/ FormBodyWrapperFilter/ DebugFilter / Servlet30WrapperFilter / SendResponseFilter / SendErrorFilter / SendForwardFilter | ZuulServerAutoConfiguration | 默认注册的基础ZuulFilter实例,之后进行专门讲解 |
ZuulRouteApplicationContextInitializer | ZuulServerAutoConfiguration | 启动时就初始化 ribbon 相关的子容器。子容器名为所有注册的服务serviceId 。 |
ZuulFilterInitializer | ZuulServerAutoConfiguration | 收集容器中的ZuulFilter 实现类,注册到 FilterRegistry 中。 |
2.2 其它注意事项
- 启用
ZuulProxyAutoConfiguration
时候,顺带就启用了Ribbon。 @EnableZuulProxy
默认引入的注解@EnableCircuitBreaker
。可以使用配置spring.cloud.circuit.breaker.enabled
进行禁用。而在启用情况下,基于Hystrix,对应使用的是HystrixCircuitBreakerConfiguration
(参见spring-cloud-netflix-hystrix-2.2.5.RELEASE.jar中的META-INFO/spring.factories 文件)。
3. Zuul 执行时
基于 ZuulHandlerMapping + ZuulController
将Zuul处理流程植入到SpringMVC主体流程,之后的用户请求,将被SpringMVC调度给ZuulServlet
,最终进入Zuul处理流程。
一图胜千言,以下为Zuul 执行时序图 。
3.1 关于ZuulServlet
- 其直接实现了
javax.servlet.http.HttpServlet
。所以在层级上它和DispatcherServlet
是平齐了。 ZuulServlet
实现的service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse)
方法中捕获异常类型为Throwable
,所以基本可以认定进入zuul处理流程的请求,不会有异常向上抛出给SpringMVC层。- 它的这个异常处理思路可以借鉴下: 自己内部产生的异常统一封装为
ZuulException
方便进行个性化处理,其它异常则向上抛出作统一处理。
4. Zuul Filter
上述流程图中,处于突出主体考虑,我们没有详细绘制出Zuul Filters。但作为灵魂所在,Zuul 对于 Filters的设计思路值得单独作为一个小节。
- Zuul Filter的执行被拆分为作为主体的pre, route, post阶段,以及发生异常时候执行的error阶段。
- 用户通过实现
ZuulFilter
抽象类来参与到Zuul的处理流程中,其中必须实现的四个抽象方法:
a.shouldFilter()
。决定在本次请求处理中,当前Filter是否需要执行。
b.run()
。本Filter的主体执行逻辑,当shouldFilter()
返回true时执行。
c.filterType()
。本Filter的执行阶段。可选:pre, route, post, error。(范例参见:FilterConstants.XXX_TYPE
)
d.filterOrder()
。在每个执行阶段,本Filter的执行优先级。 - zuul filters执行抛出异常时候:
a. pre, route filters执行抛出异常时,先执行 error filters,再执行 post filters。
b. post filters执行抛出异常时,直接执行 error filters。
4.1 内置ZuulFilter
针对常见的需求,spring-cloud-netflix-zuul提供了一系列开箱即用的filter。
Filter名称 | 类别 | 优先级 | 说明 |
---|---|---|---|
ServletDetectionFilter | pre | -3 | 检测当前请求是通过DispatcherServlet,还是ZuulServlet运行处理的。 |
Servlet30WrapperFilter | pre | -2 | 对原始的HttpServletRequest进行包装。 |
FormBodyWrapperFilter | pre | -1 | 将Content-Type为application/x-www-form-urlencoded或multipart/form-data的请求包装成FormBodyRequestWrapper对象。 |
DebugFilter | pre | 1 | 1. 根据zuul.debug.request 的配置,或当前请求参数中包含"debug=true/false"来决定是否打印debug日志。 2. 在自定义pre Filter时候,注意优先级的设置,确保在DebugFilter之后执行。 |
PreDecorationFilter | pre | 5 | 对当前请求进行预处理以便执行后续操作。最主要的是针对当前请求url,向执行上下文RequestContext 中塞入一个名为FilterConstants.SERVICE_ID_KEY 的键值对,供后续使用(典型的如:RibbonRoutingFilter )。 |
RibbonRoutingFilter | route | 10 | 通过Ribbon和Hystrix来向服务实例发起请求,并将请求结果进行返回。 |
SimpleHostRoutingFilter | route | 100 | 只对请求上下文中有routeHost参数的进行处理,直接使用HttpClient向routeHost对应的物理地址进行转发。 |
SendForwardFilter | route | 500 | 只对请求上下文中有forward.to参数的进行处理,进行本地跳转。 |
LocationRewriteFilter | post | 900 | 当返回响应状态码3xx时,重写response header中的 Location 值。 |
SendResponseFilter | post | 1000 | 利用请求上下文的响应信息来组织请求成功的响应内容。 |
SendErrorFilter | error | 0 | 当其他过滤器内部发生异常时的会由它来进行处理,产生错误响应。 |
4.2 Zuul Filter处理流程
直接上图:Zuul - ZuulFilter处理流程。
5. 关于actuate
spring-cloud-netflix-zuul 提供了对于 actuate特性的支持。
URL短路径 | 说明 | 备注 | 对应Endpoint |
---|---|---|---|
/routes | 生效的映射 | GET请求 | RoutesEndpoint |
/routes/details | 更为细致的Route信息说明 | GET请求 | RoutesEndpoint |
/routes/reset | 重置(慎用) | 1. POST请求调用。 2. 外界通过响应 RoutesRefreshedEvent 进行自定义逻辑处理。 | RoutesEndpoint |
/filters | 生效的filter | GET请求 | FiltersEndpoint |
6. 关于groovy(动态Filter)
Zuul提供了一个能够对过滤器进行动态的加载、编译、运行的框架。这些过滤器是由Groovy写成,被放在Zuul Server上的特定目录下面。Zuul会周期性轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中。这样如果要对过滤器有改动,就不用进行网关的重新发布了,只需要把过滤器上传到指定目录即可。
6.1 使用
- 引入 groovy-all 依赖。
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.5.0-beta-2</version>
</dependency>
- 初始化zuul groovy相关组件。(将以下方法设置为应用启动时候调用即可)
// initializes groovy filesystem poller (Groovy文件夹 )
private void initGroovyFilterManager() {
FilterLoader.getInstance().setCompiler(new GroovyCompiler());
//读取配置,获取脚本根目录
String scriptRoot = System.getProperty("zuul.filter.root", "groovy/filters");
if (scriptRoot.length() > 0) {
scriptRoot = scriptRoot + File.separator;
}
final List<String> filterFolderPaths = CollectionUtil.newArrayList(scriptRoot + "pre", scriptRoot + "route",
scriptRoot + "post");
filterFolderPaths.forEach(FileUtil::mkdir);
//获取刷新间隔
String refreshInterval = System.getProperty("zuul.filter.refreshInterval", "5");
try {
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(Integer.parseInt(refreshInterval), filterFolderPaths.toArray(new String[] {}));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
6.2 注意
- 只支持新增和修改,不支持删除。
- 关键类型:
a.FilterFileManager
。起专门的,名为GroovyFilterFileManagerPoller
的后台线程,周期性轮询指定Groovy文件夹,动态加载Groovy编写的ZuulFilter。
b.FilterLoader
。FilterFileManager
周期性更新的正是这个实例。而 负责进行ZuulFilter调度执行的FilterProcessor
也正是从该实例中获取处理当前请求的ZuulFilter集合,闭环。
c.GroovyCompiler
。负责将groovy脚本文件编译为Class。(在FilterLoader
中使用)
d.DefaultFilterFactory
。负责将 Class 实例化为 ZuulFilter实例。(在FilterLoader
中使用)
7. 常见配置项
zuul:
decodeUrl: true # 默认为true, 生效位置: ProxyRequestHelper.java
forceOriginalQueryStringEncoding: false # 默认为false, 生效位置: SimpleHostRoutingFilter
ignoredPatterns:
- xx
- yy
prefix: # 生效位置: SimpleRouteLocator
retryable: #
# 将zuul执行路由的调试性信息包含在response的 header中, key为FilterConstants.X_ZUUL_DEBUG_HEADER
includeDebugHeader: true
# DebugFilter中生效
debug:
request: true
# ZuulRouteApplicationContextInitializer
ribbon:
eager-load:
enabled: false
######################################################################### Hystrix
# Isolation strategy to use when executing a {@link HystrixCommand}.
# 调用 HystrixCommand 时候的隔离策略
# HystrixCommand典型实现类: OkHttpRibbonCommand
# ===== 默认配置为: SEMAPHORE
ribbon-isolation-strategy: THREAD
# 信号量
semaphore:
maxSemaphores: 100
# 线程池, 只在 ribbon-isolation-strategy 设置为 THREAD 时候起效
# 在 AbstractRibbonCommand 类中生效, 赋值给 hystrix
threadPool:
useSeparateThreadPools: false
threadPoolKeyPrefix: ''
######################################################################### zuul的超时时间配置 (好像只在 SimpleHostRoutingFilter 中生效)
# (好像只在 SimpleHostRoutingFilter 中生效)
host:
# 最大连接数。默认值是200,我们项目配置的是2000。
maxTotalConnections: 200
# 每个路由的最大连接数。默认20,我们项目配置的是500。
maxPerRouteConnections: 20
#
socket-timeout-millis: 10000
connect-timeout-millis: 2000
connectionRequestTimeoutMillis: -1