写于:2019-07-13 22:52:37 参考资料: Spring Cloud 官网 Zuul wiki
相关版本:zuul 1.3.1,spring boot 2.1.5 ,spring cloud Greenwich.SR1
一、回顾
在 《揭秘 Zuul》 中知道了 Zuul 本质是由 Servlet 和 Filter 组成。
由 Servlet 接收请求,然后通过 ZuulFilter 规则链则对请求进行包装以及处理,如:请求转发,客户端负载均衡,熔断等。
二、Zuul 过滤链执行顺序
Zuul 提供有默认的 ZuulFilter ,其中有的用来对请求进行封装,有的用来处理请求等。不同的过滤器组装成过滤链,完成请求路由转发,熔断等功能。
为了完成功能组合,zuul 针对 ZuulFilter 定义了类型,执行规则,保证多个组合的过滤器能够按照预想的逻辑执行。
ZuulFilter 的执行顺序定义
根据 pre、route、 post route 进行区分。
- pre 优先级最高。
- route 优先级次之。
- post route 优先级最低。
在每个类别中(pre or reoute or post etc) 中,又存在执行顺序 order
order 是一个数字,数字越小表示,执行优先级越高。
三、ZuulFilter 执行顺序源码实现
3.1、根据 pre、route、 post route 进行区分源码
查看源码 ZuulServlet#service 能够知道 ZuulFilter 根据类型 pre -> route -> post 优先级由高到低以此执行过滤操作。
public class ZuulServlet extends HttpServlet {public void service(......){try {preRoute();} catch (ZuulException e) {error(e);postRoute();return;}try {route();} catch (ZuulException e) {error(e);postRoute();return;}try {postRoute();} catch (ZuulException e) {error(e);return;}}}
Netflix 官方文档中,给定了一张图片同样说明了根据类型的执行顺序
3.2、根据 order执行顺序
根据类型(pre、route、post) 分类执行之后,每一个分类有各自的执行顺序排列,排列参数: filterOrder 。数字越小,优先级越高。
而这个排序在 FileLoader#getFiltersByType 根据 分区类型(pre、post等) 获取到方法中生效,相关代码如下
public class FilterLoader {public List<ZuulFilter> getFiltersByType(String filterType) {// 排序Collections.sort(list); // sort by priority}}
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {public int compareTo(ZuulFilter filter) {return Integer.compare(this.filterOrder(), filter.filterOrder());}}
四、内置 ZuulFilter
版本:Zuul 1.3.1
4.1、pre filters
| 过滤类 | 优先级(越小越优先) |
|---|---|
| ServletDetectionFilter | -3 |
| Servlet30WrapperFilter | -2 |
| FormBodyWrapperFilter | -1 |
| DebugFilter | 1 |
| PreDecorationFilter | 5 |
4.2、route filters
| 过滤类 | 优先级(越小越优先) |
|---|---|
| RibbonRoutingFilter | 10 |
| SimpleHostRoutingFilter | 100 |
| SendForwardFilter | 500 |
4.3、post filters
| 过滤类 | 优先级(越小越优先) |
|---|---|
| SendErrorFilter | 0 |
| SendResponseFilter | 1000 |
五、ZuulFilter 功能简介
版本:Zuul 1.3.1
- 在 Zuul 中所有的 Filter 都需要继承 ZuulFilter。
- 默认 ZuulFilter 是否启用有两个判断条件 1、zuul.{FilterCLass}.{FilterType}.disable :false 开启,true 关闭 2、ZuulFilter#shouldFilter 方法,返回 true 执行,返回 false 不执行
- ZuulFilter 判定成功之后执行 ZuulFilter#run() 方法
5.1、pre:ServletDetectionFilter
ZuulFilter 执行触发条件
public class ServletDetectionFilter extends ZuulFilter {// 拦截所有请求public boolean shouldFilter() { return true; }}
只要该过滤器开启,拦截所有请求进行过滤处理。
过滤执行逻辑
public class ServletDetectionFilter extends ZuulFilter {@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (!(request instanceof HttpServletRequestWrapper)&& isDispatcherServletRequest(request)) {ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);}else {ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);}return null;}private boolean isDispatcherServletRequest(HttpServletRequest request) {return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;}}
代码逻辑简单明了:判定请求来源是不是 DispatcherServlet。并将判断结果作为参数存入 RequestContext中。
扩展:在 Spring MVC 中,在请求进入到 DispatcherServlet 中时,所有的标签会被打上标签:DispatcherServlet.class.getName() + “.CONTEXT”
5.2、pre:Servlet30WrapperFilter
ZuulFilter 执行触发条件
public class Servlet30WrapperFilter extends ZuulFilter {// 拦截所有请求public boolean shouldFilter() {return true; // TODO: only if in servlet 3.0 env}}
只要该过滤器开启,拦截所有请求进行过滤处理。
过滤执行逻辑
public class Servlet30WrapperFilter extends ZuulFilter {@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (request instanceof HttpServletRequestWrapper) {request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,request);ctx.setRequest(new Servlet30RequestWrapper(request));}else if (RequestUtils.isDispatcherServletRequest()) {// If it's going through the dispatcher we need to buffer the bodyctx.setRequest(new Servlet30RequestWrapper(request));}return null;}}
判断请来源符合如下两种条件,将请求重新包装成 Servlet30RequestWrapper
- 条件1、请求本身的包装为:HttpServletRequestWrapper
- 条件2、请求是来自 DispatcherServlet 的请求。
判定请求是否来自 DispathcerServlet ,已经在 ServletDetectionFilter 中判定并存入请求参数中,直接调用 RequestUtils#isDispatcherServletRequest 判断 ture or false
5.3、pre:FormBodyWrapperFilter
ZuulFilter 执行触发条件
public class FormBodyWrapperFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();String contentType = request.getContentType();// Don't use this filter on GET methodif (contentType == null) {return false;}// Only use this filter for form data and only for multipart data in a// DispatcherServlet handlertry {MediaType mediaType = MediaType.valueOf(contentType);return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)|| (isDispatcherServletRequest(request)&& MediaType.MULTIPART_FORM_DATA.includes(mediaType));}catch (InvalidMediaTypeException ex) {return false;}}}
符合如下条件的请求才会被拦截
- 请求中的 context-type 为 application/x-www-form-urlencoded
- 请求来自 DispatcherServlet 并且 context-type 为 multipart/form-data
满足以上两个条件的一个的请求都会被拦截处理
过滤执行逻辑
public class FormBodyWrapperFilter extends ZuulFilter {@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();FormBodyRequestWrapper wrapper = null;if (request instanceof HttpServletRequestWrapper) {HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils.getField(this.requestField, request);wrapper = new FormBodyRequestWrapper(wrapped);ReflectionUtils.setField(this.requestField, request, wrapper);if (request instanceof ServletRequestWrapper) {ReflectionUtils.setField(this.servletRequestField, request, wrapper);}}else {wrapper = new FormBodyRequestWrapper(request);ctx.setRequest(wrapper);}if (wrapper != null) {ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());}return null;}}
处理的主线逻辑:将请求再一次封装,封装为 FormBodyRequestWrapper 并对 requestContext 进行 content-type 的赋值操作。(FormBodyRequestWrapper 继承 Servlet30RequestWrapper)。
5.4、pre:DebugFilter
ZuulFilter 执行触发条件
DebugFilter 是用来调试的过滤器
public class DebugFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {HttpServletRequest request = RequestContext.getCurrentContext().getRequest();if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {return true;}return ROUTING_DEBUG.get();}}
触发条件有两种:
- 全局开启调试
zuul.debug.request = true 。默认:false。对所有请求进行拦截标记。 - 针对请求设置特定参数,单一请求调试
在 request 中设置 debug=true 参数,进行单请求设定。
过滤执行逻辑
public class DebugFilter extends ZuulFilter {@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();ctx.setDebugRouting(true);ctx.setDebugRequest(true);return null;}}
DebugFilter 过滤器执行也很简单就是在 RequestContext 中设置 debugRouting 和 debugRequest 为 true
开启调试之后,会在请求上下文 RequestContext 中打上标记,可以根据标记,在之后的过滤链,甚至业务中根据调试标记进行日志打印等其他调试操作
该过滤器可以实现在线上使用 debug 模式,打印相关日志信息。只需要在请求中将 debug=true 作为请求其中一个请求参数。
5.5、pre:PreDecorationFilter
PreDecorationFilter 过滤器实现了请求转发前的数据封装。
ZuulFilter 执行触发条件
public class PreDecorationFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined// serviceId}}
过滤器的触发条件: RequestContext 中没有 FORWARD_TO_KEY 和 SERVICE_ID_KEY。
FORWARD_TO_KEY 和 SERVICE_ID_KEY 这两个值都是 PreDecorationFilter#run 执行的时候进行设置的。也就是说 被 PreDecorationFilter 处理过的请求不会被处理第二次
过滤执行逻辑
public class PreDecorationFilter extends ZuulFilter {@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());Route route = this.routeLocator.getMatchingRoute(requestURI);......// set serviceId for use in filters.route.RibbonRequestctx.set(SERVICE_ID_KEY, location);ctx.set(FORWARD_TO_KEY, forwardURI);return null;}}
根据请求 requeset 获取 Route 信息,并将先关的参数信息,如服务id SERVICE_ID_KEY ,跳转链接 forwardURI 等存入 RequestContext 中。
5.6、post:RibbonRoutingFilter
RibbonRoutingFilter 过滤器实现了通过 service id 发起 http 请求。
ZuulFilter 执行触发条件
public class RibbonRoutingFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null&& ctx.sendZuulResponse());}}
RibbonRoutingFilter 基于 Service id 的请求。请求上下文中需要有service id 该过滤器才会执行。
过滤执行逻辑
public class RibbonRoutingFilter extends ZuulFilter {@Overridepublic Object run() {RequestContext context = RequestContext.getCurrentContext();this.helper.addIgnoredHeaders();try {RibbonCommandContext commandContext = buildCommandContext(context);ClientHttpResponse response = forward(commandContext);setResponse(response);return response;}catch (ZuulException ex) {throw new ZuulRuntimeException(ex);}catch (Exception ex) {throw new ZuulRuntimeException(ex);}}}
逻辑很清晰:通过前面一些过滤器的处理,其中 PreDecorationFilter 为请求调用设置基础的 service id ,请求路径等请求调用数据。
在此数据基础上 RibbonRoutingFilter 将这些参数信息封装为 Ribbon 请求参数,并以此作为参数发起 http 请求。
5.7、post:SimpleHostRoutingFilter
SimpleHostRoutingFilter 与 RibbonRoutingFilter 一样是完成 http 请求转发,不同的是 SimpleHostRoutingFilter 中的请求基于 URL,RibbonRoutingFilter 基于 Ribbon 或者 Eureka 维护的 Service id
扩展:通过在 SimpleHostRoutingFilter 实现新的 ZuulFilter ,实现自己维护服务列表。
ZuulFilter 执行触发条件
public class SimpleHostRoutingFilter extends ZuulFilterimplements ApplicationListener<EnvironmentChangeEvent> {@Overridepublic boolean shouldFilter() {return RequestContext.getCurrentContext().getRouteHost() != null&& RequestContext.getCurrentContext().sendZuulResponse();}}
基于 URL 的请求
过滤执行逻辑
public class SimpleHostRoutingFilter extends ZuulFilterimplements ApplicationListener<EnvironmentChangeEvent> {@Overridepublic Object run() {CloseableHttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity);return null;}}
其中 SimpleHostRoutingFilter#forward 中执行逻辑为拼接 HttpClient 请求参数,然后发起 http 请求。
5.8、post:SendForwardFilter
SendForwardFilter 处理需要转发的请求。
ZuulFilter 执行触发条件
public class SendForwardFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();return ctx.containsKey(FORWARD_TO_KEY)&& !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);}}
需要本地跳转的请求的请求。
执行逻辑
public class SendForwardFilter extends ZuulFilter {@Overridepublic Object run() {try {RequestContext ctx = RequestContext.getCurrentContext();String path = (String) ctx.get(FORWARD_TO_KEY);RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);if (dispatcher != null) {ctx.set(SEND_FORWARD_FILTER_RAN, true);if (!ctx.getResponse().isCommitted()) {dispatcher.forward(ctx.getRequest(), ctx.getResponse());ctx.getResponse().flushBuffer();}}}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;}}
5.9、post:SendResponseFilter
处理正常响应
ZuulFilter 执行触发条件
public class SendResponseFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {RequestContext context = RequestContext.getCurrentContext();return context.getThrowable() == null&& (!context.getZuulResponseHeaders().isEmpty()|| context.getResponseDataStream() != null|| context.getResponseBody() != null);}}
没有异常信息,并且携带相关响应数据。也就是请求响应成功的过滤处理。
过滤执行逻辑
public class SendResponseFilter extends ZuulFilter {@Overridepublic Object run() {try {addResponseHeaders();writeResponse();}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;}}
拼接 Response ,响应数据。
5.10、error:SendErrorFilter
处理异常的响应
ZuulFilter 执行触发条件
public class SendErrorFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();// only forward to errorPath if it hasn't been forwarded to alreadyreturn ctx.getThrowable() != null&& !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);}}
异常响应时触发
执行逻辑
public class SendErrorFilter extends ZuulFilter {@Overridepublic Object run() {......request.setAttribute("javax.servlet.error.status_code", exception.getStatusCode());request.setAttribute("javax.servlet.error.exception", exception.getThrowable());request.setAttribute("javax.servlet.error.message", exception.getErrorCause());......return null;}}
进行 Response 相关异常属性设置,包括 status_code,exception,message
六、总结
zuul 本质是 ZuulServlet + ZuulFilter 的结合体。
由 ZuulServlet 链接请求,然后由 一系列 ZuulFilter 组成过滤链,对请求进行处理。
Zuul 默认提供的过滤链功能如下图:
七、扩展:zuul 如何支持 Hystrix 功能
原理很简单:Hystrix 提供的请求熔断限流处理,所以 Hystrix 和请求的发起是紧密关联的。所以 Hystrix 相关功能必定和 请求调用关联。
zuul 请求调用有三种:
- RibbonRoutingFilter ,service id 请求转发
- SimpleHostRoutingFilter, url 请求转发
- SendForwardFilter,forward 请求转发
其中支持 Hystrix 功能的是 RibbonRoutingFilter。
使用 service id 的请求转发,本质上是依赖于 Ribbon 功能实现客户端请求调用。
默认需要支持 hystrix 功能,zuul 需要通过 ribbon 维护服务实例,或者通过 eureka 维护服务实例。
7.1、zuul 使用 Ribbon 请求调用时,如何集成 Hystrix
通过 RibbonRoutingFilter#forward 中的 RibbonCommand command = this.ribbonCommandFactory.create(context); 进行追踪。
追踪到最终发起请求调用的是 RibbonCommand#execute , RibbonCommand 由 RibbonCommandFactory 工厂创建。
RibbonCommandFactory 工厂自动配置
其中 RibbonCommandFactory 有3个实现类:
- RestClientRibbonCommandFactory
- OkHttpRibbonCommandFactory
- HttpClientRibbonCommandFactory
zuul 在自动配置类 ZuulProxyAutoConfiguration 中,根据相关依赖关系进行自动配置。
聚焦其中的一个自动配置 HttpClientRibbonCommandFactory
public class RibbonCommandFactoryConfiguration {@Configuration@ConditionalOnRibbonHttpClientprotected static class HttpClientRibbonConfiguration {@Autowired(required = false)private Set<FallbackProvider> zuulFallbackProviders = Collections.emptySet();@Bean@ConditionalOnMissingBeanpublic RibbonCommandFactory<?> ribbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties) {return new HttpClientRibbonCommandFactory(clientFactory, zuulProperties,zuulFallbackProviders);}}}
自动配置会生成 RibbonCommandFactory 工厂,并将我们定义的 FallbackProvider 作为构造参数。
知识:自定义实现 hystrix fallback 功能,需要自定义类实现 FallbackProvider 接口。
有了 RibbonCommandFactory 工厂,之后就是从工厂中获取RibbonCommand 实例
聚焦 RibbonCommandFactory#create
以实现类 HttpClientRibbonCommandFactory 为例
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {@Overridepublic HttpClientRibbonCommand create(final RibbonCommandContext context) {FallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());final String serviceId = context.getServiceId();final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient(serviceId, RibbonLoadBalancingHttpClient.class);client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties,zuulFallbackProvider, clientFactory.getClientConfig(serviceId));}}
创建 HttpClientRibbonCommand ,如果请求调用服务有自定义 FallbackProvider 则将其作为构造参数。
RibbonCommand#execute 发起请求
先来看看 RibbonCommand 实现类 HttpClientRibbonCommand 相关继承关系
跟从源码 能够得到 RibbonCommand#execute 最终调用 HystrixCommand#execute 方法。
到此就能知道 Zuul 如何提供 Hystrix 熔断功能。
