一、zuul如何路由到上游服務器


所有文章

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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM