基于spring cloud 的zuul组件,源码亦是从demo中入手,方式以debug为主。java
从架构图能够在宏观总体上把握Zuul的架构设计关键概要部分,而从核心类图中能够更加细粒度的看出zuul设计中的关键点以及设计意图。程序员
ZuulConfiguration
并不是是zuul-core
的核心类,它是由spring cloud
团队开发。web
@Configuration @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) // Make sure to get the ServerProperties from the same place as a normal web app would @Import(ServerPropertiesAutoConfiguration.class) public class ZuulConfiguration { /// }
ZuulConfiguration
加载了ZuulProperties
实例,其实例是基于ZuulProperties
方式的配置类,同时查阅源码还能够看到一些关键信息类的实例,好比ZuulServlet
类实例。算法
@Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet() { ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(), this.zuulProperties.getServletPattern()); // The whole point of exposing this servlet is to provide a route that doesn't // buffer requests. servlet.addInitParameter("buffer-requests", "false"); return servlet; }
ZuulProxyConfiguration
继承自ZuulProxyConfiguration
,内部建立了PreDecorationFilter
,RibbonRoutingFilter
,SimpleHostRoutingFilter
三个很是重要的ZuulFilter
实例spring
// pre filters @Bean public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties, proxyRequestHelper); } // route filters @Bean public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<?> ribbonCommandFactory) { RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers); return filter; } @Bean public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) { return new SimpleHostRoutingFilter(helper, zuulProperties); }
几乎将全部的核心类的实例都在Configuration类中建立,代码阅读起来很是清晰明朗,这种方式就是基于Java config的bean实例化方式,上一代是基于XML方式。设计模式
ZuulServlet
的实例建立与ZuulConfiguration
中,该设计实现思路彻底能够类比SpringMVC
中的DispatchServlet
。架构
在ZuulServlet
中,zuul作了什么事?app
@Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the "Zuul engine", as opposed to servlets // explicitly bound in web.xml, for which requests will not have the same data attached RequestContext context = RequestContext.getCurrentContext();//建立request上下文对象,内部基于ThreadLocal实现上下文线程隔离 context.setZuulEngineRan(); 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; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
首先,查看 过滤器中顶级接口IZuulFilter
能够发现,zuul中所指的并不是是javax.servlet.filter
,而是内部设计接口,命名上容易让人产生疑惑,本质原理是不一样,但它们的道理是相同的——>链式过滤器。负载均衡
该抽象类提供了几个很是重要的方法。ide
abstract public String filterType();// 过滤器类型 abstract public int filterOrder();// 过滤器排序值 boolean shouldFilter();// 是否过滤 Object run(); // 继承自IZuulFilter接口 // 经过模板方法设计模式定义了过滤器的执行方式 public ZuulFilterResult runFilter() { ZuulFilterResult zr = new ZuulFilterResult(); if (!isFilterDisabled()) { if (shouldFilter()) { Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName()); try { Object res = run(); zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS); } catch (Throwable e) { t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed"); zr = new ZuulFilterResult(ExecutionStatus.FAILED); zr.setException(e); } finally { t.stopAndLog(); } } else { zr = new ZuulFilterResult(ExecutionStatus.SKIPPED); } } return zr; }
// 该方法决定了是否通过这个过滤器 // 条件是当前的RequestContext不能包含`forward.to`跟`serviceid`的值 // 注意注解信息, @Override public 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 } // 这个过滤器的处理方式是,取出请求路径,经过请求路径找到配置项中的配置信息 // 详细能够查看Router类中的信息 // 经过routerhose 或者 serviceId 来决定routerFilter @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest()); Route route = this.routeLocator.getMatchingRoute(requestURI); // 注意看这段代码,设置了routerhost if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) { ctx.setRouteHost(getUrl(location)); ctx.addOriginResponseHeader(SERVICE_HEADER, location); } // 注意这段代码,设置了serviceId ctx.set(SERVICE_ID_KEY, location); ctx.setRouteHost(null); ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location); }
public class SimpleHostRoutingFilter extends ZuulFilter { // 套接字超时默认时间 private static final DynamicIntProperty SOCKET_TIMEOUT = DynamicPropertyFactory .getInstance() .getIntProperty(ZuulConstants.ZUUL_HOST_SOCKET_TIMEOUT_MILLIS, 10000); // 链接超时默认时间 private static final DynamicIntProperty CONNECTION_TIMEOUT = DynamicPropertyFactory .getInstance() .getIntProperty(ZuulConstants.ZUUL_HOST_CONNECT_TIMEOUT_MILLIS, 2000); @Override public String filterType() { return ROUTE_TYPE; } @Override public int filterOrder() { return SIMPLE_HOST_ROUTING_FILTER_ORDER; } @Override public boolean shouldFilter() { // RequestContext上下文中包含了routehost信息并sendZuulResponse为true return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); } @Override public Object run() { .... try { // 从源码能够看到,本质上,请求也是经过httpclient处理的 CloseableHttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity); setResponse(response); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } return null; } } //注意网飞公司的项目有一个叫Ribbon的负载均衡项目,那么这里的过滤器是否也支持负载均衡呢? public class RibbonRoutingFilter extends ZuulFilter { private static final Log log = LogFactory.getLog(RibbonRoutingFilter.class); @Override public String filterType() { return ROUTE_TYPE; } @Override public int filterOrder() { return RIBBON_ROUTING_FILTER_ORDER; } @Override public boolean shouldFilter() { // RequestContext上下文中没有routehost,且serviceId不为空,并sendZuulResponse为true RequestContext ctx = RequestContext.getCurrentContext(); return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null && ctx.sendZuulResponse()); } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); this.helper.addIgnoredHeaders(); try { // 经过查看RibbonCommand接口,能够发现该接口扩展了HystrixExecutable接口,Hystrix是网飞的一个熔断器项目 // 也就说RibbonRoutingFilter过滤器是支持熔断隔离,负载均衡等策略的转发器 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); } } protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception { Map<String, Object> info = this.helper.debug(context.getMethod(), context.getUri(), context.getHeaders(), context.getParams(), context.getRequestEntity()); // 默认HttpClientRibbonCommandFactory // HttpClientRibbonCommand // AbstractRibbonCommand // ->HystrixCommand 熔断降级等策略 // ->AbstractLoadBalancerAwareClient // ->LoadBalancerContext // ->ILoadBalancer#chooseServer 负载均衡策略 RibbonCommand command = this.ribbonCommandFactory.create(context); try { ClientHttpResponse response = command.execute(); this.helper.appendDebug(info, response.getStatusCode().value(), response.getHeaders()); return response; } catch (HystrixRuntimeException ex) { return handleException(info, ex); } } }
经过源码分析能够很是明显的了解到,经过配置能够决定用哪一个转发器,不一样的转发决定了转发的策略。
SimpleHostRoutingFilter
内置httpclient
的普通请求方式,自有一套适用的场景,而RibbonRoutingFilter
相对功能更加齐全完善,具有负载均衡/降级熔断的策略,首选上天然是RibbonRoutingFilter
SendResponseFilter
该类主要是处理header跟response
@Override public boolean shouldFilter() { RequestContext context = RequestContext.getCurrentContext(); return context.getThrowable() == null && (!context.getZuulResponseHeaders().isEmpty() || context.getResponseDataStream() != null || context.getResponseBody() != null); } @Override public Object run() { try { addResponseHeaders(); writeResponse(); } catch (Exception ex) { ReflectionUtils.rethrowRuntimeException(ex); } return null; }
核心类基本上介绍自此,其余的更多能够参照类图自行去了解,看源码处理学习项目基础架构,基本原理,同时也须要去关注他们的代码架构,,不少国外工程师写的项目在代码追求上更优质,好比大名鼎鼎的spring
,设计模式满天飞。
-一、本文并未涉及热加载等原理,详细的加载机制能够查阅相关类图 0、本文并未涉及httpclient
具体的源码分析 一、本文并未涉及Ribbon
具体的负载均衡算法 二、本文并未涉及Hystrix
的熔断策略分析 三、zuul已经出了2.0,应该考虑升级优质版本 四、求技术大佬们不要动不动就发展新技术,程序员能不能对同行友善一点?
下一篇应该会考虑学习分析熔断机制的原理