Zuul 架構圖 前端
在zuul中,整個請求的過程是這樣的,java
zuul工做原理源碼分析 使用zuul,其中不可缺乏的一個步驟就是在程序的啓動類加上@EnableZuulProxy,該EnableZuulProxy類代碼以下:web
@EnableCircuitBreaker @EnableDiscoveryClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(ZuulProxyConfiguration.class) public @interface EnableZuulProxy { }
其中,引用了ZuulProxyConfiguration,跟蹤ZuulProxyConfiguration,該類注入了DiscoveryClient、RibbonCommandFactoryConfiguration用做負載均衡相關的。注入了一些列的filters,好比PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter,代碼如以下:spring
@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); }
它的父類ZuulConfiguration ,引用了一些相關的配置。在缺失zuulServlet bean的狀況下注入了ZuulServlet,該類是zuul的核心類。api
@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; }
同時也注入了其餘的過濾器,好比ServletDetectionFilter、DebugFilter、Servlet30WrapperFilter,這些過濾器都是pre類型的。網絡
@Bean public ServletDetectionFilter servletDetectionFilter() { return new ServletDetectionFilter(); } @Bean public FormBodyWrapperFilter formBodyWrapperFilter() { return new FormBodyWrapperFilter(); } @Bean public DebugFilter debugFilter() { return new DebugFilter(); } @Bean public Servlet30WrapperFilter servlet30WrapperFilter() { return new Servlet30WrapperFilter(); }
它也注入了post類型的,好比 SendResponseFilter,error類型,好比 SendErrorFilter,route類型好比SendForwardFilter,代碼以下:架構
@Bean public SendResponseFilter sendResponseFilter() { return new SendResponseFilter(); } @Bean public SendErrorFilter sendErrorFilter() { return new SendErrorFilter(); } @Bean public SendForwardFilter sendForwardFilter() { return new SendForwardFilter(); }
初始化ZuulFilterInitializer類,將全部的filter 向FilterRegistry註冊。app
@Configuration protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters; @Bean public ZuulFilterInitializer zuulFilterInitializer( CounterFactory counterFactory, TracerFactory tracerFactory) { FilterLoader filterLoader = FilterLoader.getInstance(); FilterRegistry filterRegistry = FilterRegistry.instance(); return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry); } }
而FilterRegistry管理了一個ConcurrentHashMap,用做存儲過濾器的,並有一些基本的CURD過濾器的方法,代碼以下:負載均衡
public class FilterRegistry { private static final FilterRegistry INSTANCE = new FilterRegistry(); public static final FilterRegistry instance() { return INSTANCE; } private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>(); private FilterRegistry() { } public ZuulFilter remove(String key) { return this.filters.remove(key); } public ZuulFilter get(String key) { return this.filters.get(key); } public void put(String key, ZuulFilter filter) { this.filters.putIfAbsent(key, filter); } public int size() { return this.filters.size(); } public Collection<ZuulFilter> getAllFilters() { return this.filters.values(); } }
FilterLoader類持有FilterRegistry,FilterFileManager類持有FilterLoader,因此最終是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager到開啓了輪詢機制,定時的去加載過濾器,代碼以下:ide
void startPoller() { poller = new Thread("GroovyFilterFileManagerPoller") { public void run() { while (bRunning) { try { sleep(pollingIntervalSeconds * 1000); manageFiles(); } catch (Exception e) { e.printStackTrace(); } } } }; poller.setDaemon(true); poller.start(); }
Zuulservlet做爲相似於Spring MVC中的DispatchServlet,起到了前端控制器的做用,全部的請求都由它接管。它的核心代碼以下:
@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(); 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(); } }
跟蹤init(),能夠發現這個方法爲每一個請求生成了RequestContext,RequestContext繼承了ConcurrentHashMap
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { RequestContext ctx = RequestContext.getCurrentContext(); if (bufferRequests) { ctx.setRequest(new HttpServletRequestWrapper(servletRequest)); } else { ctx.setRequest(servletRequest); } ctx.setResponse(new HttpServletResponseWrapper(servletResponse)); } public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute(); }
而FilterProcessor類爲調用filters的類,好比調用pre類型全部的過濾器:
public void preRoute() throws ZuulException { try { runFilters("pre"); } catch (ZuulException e) { throw e; } catch (Throwable e) { throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName()); } }
跟蹤runFilters()方法,能夠發現,它最終調用了FilterLoader的getFiltersByType(sType)方法來獲取同一類的過濾器,而後用for循環遍歷全部的ZuulFilter,執行了 processZuulFilter()方法,跟蹤該方法能夠發現最終是執行了ZuulFilter的方法,最終返回了該方法返回的Object對象。
public Object runFilters(String sType) throws Throwable { if (RequestContext.getCurrentContext().debugRouting()) { Debug.addRoutingDebug("Invoking {" + sType + "} type filters"); } boolean bResult = false; List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType); if (list != null) { for (int i = 0; i < list.size(); i++) { ZuulFilter zuulFilter = list.get(i); Object result = processZuulFilter(zuulFilter); if (result != null && result instanceof Boolean) { bResult |= ((Boolean) result); } } } return bResult; }
route、post類型的過濾器的執行過程和pre執行過程相似。
Zuul默認過濾器 Zuul默認注入的過濾器,它們的執行順序在FilterConstants類,咱們能夠先定位在這個類,而後再看這個類的過濾器的執行順序以及相關的註釋,能夠很輕鬆定位到相關的過濾器,也能夠直接打開 spring-cloud-netflix-core.jar的 zuul.filters包,能夠看到一些列的filter,如今我以表格的形式,列出默認注入的filter.
過濾器的order值越小,就越先執行,而且在執行過濾器的過程當中,它們共享了一個RequestContext對象,該對象的生命週期貫穿於請求,能夠看出優先執行了pre類型的過濾器,並將執行後的結果放在RequestContext中,供後續的filter使用,好比在執行PreDecorationFilter的時候,決定使用哪個route,它的結果的是放在RequestContext對象中,後續會執行全部的route的過濾器,若是不知足條件就不執行該過濾器的run方法。最終達到了就執行一個route過濾器的run()方法。
而error類型的過濾器,是在程序發生異常的時候執行的。
post類型的過濾,在默認的狀況下,只注入了SendResponseFilter,該類型的過濾器是將最終的請求結果以流的形式輸出給客戶單。
如今來看SimpleHostRoutingFilter是如何工做?
進入到SimpleHostRoutingFilter類的方法的run()方法,核心代碼以下:
@Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); //省略代碼 String uri = this.helper.buildZuulRequestURI(request); this.helper.addIgnoredHeaders(); try { CloseableHttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity); setResponse(response); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } return null; }
查閱這個類的所有代碼可知,該類建立了一個HttpClient做爲請求類,並重構了url,請求到了具體的服務,獲得的一個CloseableHttpResponse對象,並將CloseableHttpResponse對象的保存到RequestContext對象中。並調用了ProxyRequestHelper的setResponse方法,將請求狀態碼,流等信息保存在RequestContext對象中。
private void setResponse(HttpResponse response) throws IOException { RequestContext.getCurrentContext().set("zuulResponse", response); this.helper.setResponse(response.getStatusLine().getStatusCode(), response.getEntity() == null ? null : response.getEntity().getContent(), revertHeaders(response.getAllHeaders())); }
如今來看SendResponseFilter是如何工做?
這個過濾器的order爲1000,在默認且正常的狀況下,是最後一個執行的過濾器,該過濾器是最終將獲得的數據返回給客戶端的請求。
在它的run()方法裏,有兩個方法:addResponseHeaders()和writeResponse(),即添加響應頭和寫入響應數據流。
public Object run() { try { addResponseHeaders(); writeResponse(); } catch (Exception ex) { ReflectionUtils.rethrowRuntimeException(ex); } return null; }
其中writeResponse()方法是經過從RequestContext中獲取ResponseBody獲或者ResponseDataStream來寫入到HttpServletResponse中的,可是在默認的狀況下ResponseBody爲null,而ResponseDataStream在route類型過濾器中已經設置進去了。具體代碼以下:
private void writeResponse() throws Exception { RequestContext context = RequestContext.getCurrentContext(); HttpServletResponse servletResponse = context.getResponse(); //代碼省略 OutputStream outStream = servletResponse.getOutputStream(); InputStream is = null; try { if (RequestContext.getCurrentContext().getResponseBody() != null) { String body = RequestContext.getCurrentContext().getResponseBody(); writeResponse( new ByteArrayInputStream( body.getBytes(servletResponse.getCharacterEncoding())), outStream); return; } //代碼省略 is = context.getResponseDataStream(); InputStream inputStream = is; //代碼省略 writeResponse(inputStream, outStream); //代碼省略 } } ..//代碼省略 }
如何在zuul上作日誌處理? 因爲zuul做爲api網關,全部的請求都通過這裏,因此在網關上,能夠作請求相關的日誌處理。 個人需求是這樣的,須要記錄請求的 url,ip地址,參數,請求發生的時間,整個請求的耗時,請求的響應狀態,甚至請求響應的結果等。 很顯然,須要實現這樣的一個功能,須要寫一個ZuulFliter,它應該是在請求發送給客戶端以前作處理,而且在route過濾器路由以後,在默認的狀況下,這個過濾器的order應該爲500-1000之間。那麼如何獲取這些我須要的日誌信息呢?找RequestContext,在請求的生命週期裏這個對象裏,存儲了整個請求的全部信息。
@Component public class LoggerFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.POST_TYPE; } @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String method = request.getMethod();//氫氣的類型,post get .. Map<String, String> params = HttpUtils.getParams(request); String paramsStr = params.toString();//請求的參數 long statrtTime = (long) context.get("startTime");//請求的開始時間 Throwable throwable = context.getThrowable();//請求的異常,若是有的話 request.getRequestURI();//請求的uri HttpUtils.getIpAddress(request);//請求的iP地址 context.getResponseStatusCode();//請求的狀態 long duration=System.currentTimeMillis() - statrtTime);//請求耗時 return null; } }
如今讀者也許有疑問,如何獲得的statrtTime,即請求開始的時間,其實這須要另一個過濾器,在網絡請求route以前(大部分耗時都在route這一步),在過濾器中,在RequestContext存儲一個時間便可,另寫一個過濾器,代碼以下:
@Component public class AccessFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); ctx.set("startTime",System.currentTimeMillis()); return null; } }
可能還有這樣的需求,我須要將響應結果,也要存儲在log中,在以前已經分析了,在route結束後,將從具體服務獲取的響應流存儲在RequestContext中,在SendResponseFilter過濾器寫入在HttpServletResponse中,最終返回給客戶端。那麼我只須要在SendResponseFilter寫入響應流以前把響應流寫入到 log日誌中便可,那麼會引起另一個問題,由於響應流寫入到 log後,RequestContext就沒有響應流了,在SendResponseFilter就沒有流輸入到HttpServletResponse中,致使客戶端沒有任何的返回數據,那麼解決的辦法是這樣的:
InputStream inputStream =RequestContext.getCurrentContext().getResponseDataStream(); InputStream newInputStream= copy(inputStream); transerferTolog(inputStream); RequestContext.getCurrentContext().setResponseDataStream(newInputStream);
從RequestContext獲取到流以後,首先將流 copy一份,將流轉化下字符串,存在日誌中,再set到RequestContext中, 這樣SendResponseFilter就能夠將響應返回給客戶端。這樣的作法有點影響性能,若是不是字符流,可能須要作更多的處理工做。