所有文章
https://www.cnblogs.com/lay2017/p/11908715.html
正文
zuul在分布式項目中充當着一個網關的角色,而它最主要的功能像nginx一樣針對上游服務器做反向代理。我們可以將它理解為一個服務的門面,作為客戶端來說不需要再面向各式各樣的服務,只需要面向zuul即可,簡化了客戶端與服務端的交互關系。
既然,zuul成為了客戶端與服務端的中間層,那么zuul顯然可以進行攔截、記錄、安全管理、路由...等等各種處理。本文,將從路由這個點切入,看看路由的過程。
ZuulServlet
首先,客戶端和服務端的交互顯然少不了的http,所以先找到zuul針對Servlet的實現
可以看到,ZuulServlet直接繼承了HttpServlet。所以,ZuulServlet依然走的是http通信協議,我們跟進ZuulServlet的service方法。
@Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // 初始化一個上下文 RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); // 路由前置處理------------------- try { // pre類型的ZuulFilter preRoute(); } catch (ZuulException e) { // error類型的ZuulFilter error(e); // post類型的ZuulFilter postRoute(); return; } // 路由處理----------------------- try { // route類型的ZuulFilter route(); } catch (ZuulException e) { // error類型的ZuulFilter error(e); // post類型的ZuulFilter postRoute(); return; } // 路由后置處理-------------------- try { // post類型的ZuulFilter postRoute(); } catch (ZuulException e) { // error類型的ZuulFilter error(e); return; } } catch (Throwable e) { // ... } finally { RequestContext.getCurrentContext().unset(); } }
顯然,service方法很清晰地描繪了一個這樣的路由過程:
瀏覽器發起響應 -> preFilter -> routeFilter -> postFilter -> 瀏覽器接受響應
|---------|-----------|-------> errorFilter -> 瀏覽器接受響應
PreDecorationFilter
preFilter無非就是對Servlet的請求信息進行處理,為routeFilter做准備。默認的preFilter有這么5個
這里我們以PreDecorationFilter為例,看看它的處理過程。
public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest()); // 獲取路由信息 Route route = this.routeLocator.getMatchingRoute(requestURI); if (route != null) { // ... 處理路由信息,添加到context當中 } else { // ... } return null; }
PreDecorationFilter主要是做了一個路由准備。例如:http://localhost:8080/consumer/user/get?userId=1
這里的route信息將會是
經過PreDecorationFilter以后,我們已經知道了一個請求該路由到哪里去。
RibbonRoutingFilter
routeFilter默認有以下三種,這里以RibbonRoutingFilter為例
跟進RibbonRoutingFilter的run方法
@Override public 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); } }
run方法中做了一次請求轉發,我們跟進forward看看
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception { Map<String, Object> info = this.helper.debug(context.getMethod(), context.getUri(), context.getHeaders(), context.getParams(), context.getRequestEntity()); // 構造RibbonCommand RibbonCommand command = this.ribbonCommandFactory.create(context); try { // 執行RibbonCommand ClientHttpResponse response = command.execute(); return response; } catch (HystrixRuntimeException ex) { return handleException(info, ex); } }
這里構造並執行了一個RibbonComand,具體的實例對象是HttpClientRibbonCommand,我們看看它的類圖
HttpClientRibbonCommand主要是包含了三種實現
1、ClientRequest:實現了請求響應
2、RibbonCommand表示了一個負載均衡的實現
3、HystrixCommand表示了一個熔斷的實現
到這里我們基本可以知道HttpClientRibbonCommand的請求過程
Hystrix熔斷前置判斷 -> Ribbon負載均衡處理 -> http請求到上游服務 -> 返回響應結果 -> 設置到上下文當中
SendResponseFilter
經過routeFilter以后,我們已經獲得了上游服務器的response結果。然后就是postFilter,默認的postFilter只有一個SendResponseFilter,顧名思義其實就是發送響應結果返回到客戶端。
打開SendResponseFilter的run方法
@Override public Object run() { try { addResponseHeaders(); writeResponse(); } catch (Exception ex) { ReflectionUtils.rethrowRuntimeException(ex); } return null; }
只做了一件事,寫入響應數據,跟進writeResponse方法
private void writeResponse() throws Exception { RequestContext context = RequestContext.getCurrentContext(); // ... HttpServletResponse servletResponse = context.getResponse(); // ... OutputStream outStream = servletResponse.getOutputStream(); // 獲取輸入流 InputStream is = null; try { if (context.getResponseBody() != null) { String body = context.getResponseBody(); // 響應內容轉化為字節流 is = new ByteArrayInputStream(body.getBytes(servletResponse.getCharacterEncoding())); } else { // ... } // ... if (is != null) { // 寫入響應流 writeResponse(is, outStream); } } finally { // 清理... } }
這里生成了字節流並寫入outStream,繼續跟進writeResponse
private void writeResponse(InputStream zin, OutputStream out) throws Exception { byte[] bytes = buffers.get(); int bytesRead = -1; while ((bytesRead = zin.read(bytes)) != -1) { out.write(bytes, 0, bytesRead); } }
單純地寫入輸出流
總結
Zuul作為網關,主要實現都包含在了ZuulFilter的實現當中。以一個ThreadLocal實現的RequestContext來傳遞節點數據。如果想做一些自定義的處理可以通過實現ZuulFilter。