基於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,應該考慮升級優質版本 四、求技術大佬們不要動不動就發展新技術,程序員能不能對同行友善一點?
下一篇應該會考慮學習分析熔斷機制的原理