我是一個請求,我是如何被髮送的?

摘要:本文主要分析使用cse提供的RestTemplate的場景,其實cse提供的rpc註解(RpcReference)的方式最後的調用邏輯和RestTemplate是異曲同工的。

本文分享自華爲雲社區《我是一個請求,我該何去何從(下)》,原文做者:向昊 。react

上次咱們大概瞭解到了服務端是怎麼處理請求的,那麼發送請求又是個什麼樣的流程了?本文主要分析使用cse提供的RestTemplate的場景,其實cse提供的rpc註解(RpcReference)的方式最後的調用邏輯和RestTemplate是異曲同工的。web

使用

使用cse提供的RestTemplate時候,是這樣初始化的:spring

RestTemplate restTemplate = RestTemplateBuilder.create();

restTemplate.getForObject("cse://appId:serviceName/xxx", Object.class);

咱們能夠注意到2個怪異的地方:apache

  • RestTemplate是經過RestTemplateBuilder.create()來獲取的,而不是用的Spring裏提供的。
  • 請求路徑開頭是cse而不是咱們常見的http、https且須要加上服務所屬的應用ID和服務名稱。

解析

根據url匹配RestTemplate

首先看下RestTemplateBuilder.create(),它返回的是org.apache.servicecomb.provider.springmvc.reference.RestTemplateWrapper,是cse提供的一個包裝類。c#

// org.apache.servicecomb.provider.springmvc.reference.RestTemplateWrapper
// 用於同時支持cse調用和非cse調用
class RestTemplateWrapper extends RestTemplate {
    private final List<AcceptableRestTemplate> acceptableRestTemplates = new ArrayList<>();
 
    final RestTemplate defaultRestTemplate = new RestTemplate();
 
    RestTemplateWrapper() {
        acceptableRestTemplates.add(new CseRestTemplate());
    }
 
    RestTemplate getRestTemplate(String url) {
        for (AcceptableRestTemplate template : acceptableRestTemplates) {
            if (template.isAcceptable(url)) {
                return template;
            }
        }
        return defaultRestTemplate;
    }
}

AcceptableRestTemplate:這個類是一個抽象類,也是繼承RestTemplate的,目前其子類就是CseRestTemplate,咱們也能夠看到在初始化的時候會默認往acceptableRestTemplates中添加一個CseRestTemplate。tomcat

回到使用的地方restTemplate.getForObject:這個方法會委託給以下方法:網絡

public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException {
    return getRestTemplate(url).getForObject(url, responseType, urlVariables);
}

能夠看到首先會調用getRestTemplate(url),即會調用template.isAcceptable(url),若是匹配到了就返回CseRestTemplate,不然就返回常規的RestTemplate。那麼再看下isAcceptable()這個方法:mvc

到這裏咱們就清楚了路徑中的cse://的做用了,就是爲了使用CseRestTemplate來發起請求,也理解了爲啥RestTemplateWrapper能夠同時支持cse調用和非cse調用。app

委託調用

從上面可知,咱們的cse調用其實都是委託給CseRestTemplate了。在構造CseRestTemplate的時候會初始化幾個東西:異步

public CseRestTemplate() {
    setMessageConverters(Arrays.asList(new CseHttpMessageConverter()));
    setRequestFactory(new CseClientHttpRequestFactory());
    setUriTemplateHandler(new CseUriTemplateHandler());
}

這裏須要重點關注new CseClientHttpRequestFactory():

public class CseClientHttpRequestFactory implements ClientHttpRequestFactory {
    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        return new CseClientHttpRequest(uri, httpMethod);
    }
}

最終委託到了CseClientHttpRequest這個類,這裏就是重頭戲了!

咱們先把注意力拉回到這句話:restTemplate.getForObject("cse://appId:serviceName/xxx", Object.class),從上面咱們知道其邏輯是先根據url找到對應的RestTemplate,而後調用getForObject這個方法,最終這個方法會調用到:org.springframework.web.client.RestTemplate#doExecute:

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
    @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
 
    ClientHttpResponse response = null;
    try {
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
            requestCallback.doWithRequest(request);
        }
        response = request.execute();
        handleResponse(url, method, response);
        return (responseExtractor != null ? responseExtractor.extractData(response) : null);
    }
}

createRequest(url, method):會調用getRequestFactory().createRequest(url, method),即最終會調用到咱們初始化CseClientHttpRequest是塞的RequestFactory,因此這裏會返回ClientHttpRequest這個類。

request.execute():這個方法會委託到org.apache.servicecomb.provider.springmvc.reference.CseClientHttpRequest#execute這個方法上。

至此咱們知道前面的調用最終會委託到CseClientHttpRequest#execute這個方法上。

cse調用

接着上文分析:

public ClientHttpResponse execute() {
    path = findUriPath(uri);
    requestMeta = createRequestMeta(method.name(), uri);
 
    QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri.getRawSchemeSpecificPart());
    queryParams = queryStringDecoder.parameters();
 
    Object[] args = this.collectArguments();
 
    // 異常流程,直接拋異常出去
    return this.invoke(args);
}

createRequestMeta(method.name(), uri):這裏主要是根據microserviceName去獲取調用服務的信息,並會將獲取的信息放入到Map中。服務信息以下:

能夠看到裏面的信息很豐富,例如應用名、服務名、還有接口對應的yaml信息等。

this.collectArguments():這裏隱藏了一個校驗點,就是會校驗傳入的參數是否符合對方接口的定義。主要是經過這個方法:org.apache.servicecomb.common.rest.codec.RestCodec#restToArgs,若是不符合真個流程就結束了。

準備invocation

從上面分析可知,獲取到接口所需的參數後就會調用這個方法:org.apache.servicecomb.provider.springmvc.reference.CseClientHttpRequest#invoke:

private CseClientHttpResponse invoke(Object[] args) {
    Invocation invocation = prepareInvocation(args);
    Response response = doInvoke(invocation);
 
    if (response.isSuccessed()) {
        return new CseClientHttpResponse(response);
    }
 
    throw ExceptionFactory.convertConsumerException(response.getResult());
}

prepareInvocation(args):這個方法會準備好Invocation,這個Invocation在上集已經分析過了,不過上集中的它是爲服務端服務的,那麼我們這塊固然就得爲消費端工做了

protected Invocation prepareInvocation(Object[] args) {
    Invocation invocation =
        InvocationFactory.forConsumer(requestMeta.getReferenceConfig(),
            requestMeta.getOperationMeta(),
            args);
 
    return invocation;
}

從名字也能夠看出它是爲消費端服務的,其實不管是forProvider仍是forConsumer,它們最主要的區別就是加載的Handler不一樣,此次加載的Handler以下:

  • class org.apache.servicecomb.qps.ConsumerQpsFlowControlHandler(流控)
  • class org.apache.servicecomb.loadbalance.LoadbalanceHandler(負載)
  • class org.apache.servicecomb.bizkeeper.ConsumerBizkeeperHandler(容錯)
  • class org.apache.servicecomb.core.handler.impl.TransportClientHandler(調用,默認加載的)

前面3個Handler 能夠參考下這個微服務治理專欄

doInvoke(invocation):初始化好了invocation後就開始調用了。最終會調用到這個方法上:org.apache.servicecomb.core.provider.consumer.InvokerUtils#innerSyncInvoke

至此,這些動做就是cse中RestTemplate和rpc調用的不一樣之處。不過能夠清楚的看到RestTemplate的方式是隻支持同步的,即innerSyncInvoke,可是rpc是能夠支持異步的,即reactiveInvoke

public static Response innerSyncInvoke(Invocation invocation) {
 
    invocation.next(respExecutor::setResponse);
}

到這裏咱們知道了,消費端發起請求仍是得靠invocation的責任鏈驅動

啓動invocation責任鏈

好了,我們的老朋友又出現了:invocation.next,這個方法是個典型的責任鏈模式,其鏈條就是上面說的那4個Handler。前面3個就不分析了,直接跳到TransportClientHandler。

// org.apache.servicecomb.core.handler.impl.TransportClientHandler
public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception {
    Transport transport = invocation.getTransport();
    transport.send(invocation, asyncResp);
}

invocation.getTransport():獲取請求地址,即最終發送請求的時候仍是以ip:port的形式。

transport.send(invocation, asyncResp):調用鏈爲

org.apache.servicecomb.transport.rest.vertx.VertxRestTransport#send

  • ->org.apache.servicecomb.transport.rest.client.RestTransportClient#send(這裏會初始化HttpClientWithContext,下面會分析)
  • ->org.apache.servicecomb.transport.rest.client.http.RestClientInvocation#invoke(真正發送請求的地方)
public void invoke(Invocation invocation, AsyncResponse asyncResp) throws Exception {
 
    createRequest(ipPort, path);
    clientRequest.putHeader(org.apache.servicecomb.core.Const.TARGET_MICROSERVICE, invocation.getMicroserviceName());
    RestClientRequestImpl restClientRequest =
        new RestClientRequestImpl(clientRequest, httpClientWithContext.context(), asyncResp, throwableHandler);
    invocation.getHandlerContext().put(RestConst.INVOCATION_HANDLER_REQUESTCLIENT, restClientRequest);
 
    Buffer requestBodyBuffer = restClientRequest.getBodyBuffer();
    HttpServletRequestEx requestEx = new VertxClientRequestToHttpServletRequest(clientRequest, requestBodyBuffer);
    invocation.getInvocationStageTrace().startClientFiltersRequest();
    // 觸發filter.beforeSendRequest方法
    for (HttpClientFilter filter : httpClientFilters) {
        if (filter.enabled()) {
            filter.beforeSendRequest(invocation, requestEx);
        }
    }
 
    // 從業務線程轉移到網絡線程中去發送
    // httpClientWithContext.runOnContext
}

createRequest(ipPort, path):根據參數初始化HttpClientRequest clientRequest,初始化的時候會傳入一個建立一個responseHandler,即對響應的處理。

注意org.apache.servicecomb.common.rest.filter.HttpClientFilter#afterReceiveResponse的調用就是在這裏埋下伏筆的,是經過回調org.apache.servicecomb.transport.rest.client.http.RestClientInvocation#processResponseBody這個方法觸發的(在建立responseHandler時候建立的)

且org.apache.servicecomb.common.rest.filter.HttpClientFilter#beforeSendRequest:這個方法的觸發咱們也能夠很清楚的看到在發送請求執行的。

requestEx:注意它的類型是HttpServletRequestEx,雖然名字裏面帶有Servlet,可是打開它的方法能夠發現有不少咱們在tomcat中那些經常使用的方法都直接拋出異常了,這也是一個易錯點!

httpClientWithContext.runOnContext:用來發送請求的邏輯,不過這裏仍是有點繞的,下面重點分析下

httpClientWithContext.runOnContext

首先看下HttpClientWithContext的定義:

public class HttpClientWithContext {
    public interface RunHandler {
        void run(HttpClient httpClient);
    }
 
    private HttpClient httpClient;
 
    private Context context;
 
    public HttpClientWithContext(HttpClient httpClient, Context context) {
        this.httpClient = httpClient;
        this.context = context;
    }
 
    public void runOnContext(RunHandler handler) {
        context.runOnContext((v) -> {
            handler.run(httpClient);
        });
    }
}

從上面可知發送請求調用的是這個方法:runOnContext,參數爲RunHandler接口,而後是以lambda的方式傳入的,lambda的參數爲httpClient,這個httpClient又是在HttpClientWithContext的構造函數中初始化的。這個構造函數是在org.apache.servicecomb.transport.rest.client.RestTransportClient#send這個方法中初始化的(調用org.apache.servicecomb.transport.rest.client.RestTransportClient#findHttpClientPool這個方法)。

可是咱們觀察調用的地方:

// 從業務線程轉移到網絡線程中去發送
httpClientWithContext.runOnContext(httpClient -> {
    clientRequest.setTimeout(operationMeta.getConfig().getMsRequestTimeout());
    processServiceCombHeaders(invocation, operationMeta);
    try {
        restClientRequest.end();
    } catch (Throwable e) {
        LOGGER.error(invocation.getMarker(),
            "send http request failed, local:{}, remote: {}.", getLocalAddress(), ipPort, e);
        fail((ConnectionBase) clientRequest.connection(), e);
    }
});

其實在這塊邏輯中HttpClient是沒有被用到的,實際上發送請求的動做是restClientRequest.end()觸發的,restClientRequest是cse中的類RestClientRequestImpl,而後它包裝了HttpClientRequest(vertx中提供的),即restClientRequest.end()最終仍是委託到了HttpClientRequest.end()上了。

那麼這個HttpClientRequest是怎麼被初始化的了?它是在createRequest(ipPort, path)這個方法中初始化的,即在調用org.apache.servicecomb.transport.rest.client.http.RestClientInvocation#invoke方法入口處。

初始化的邏輯以下:

clientRequest = httpClientWithContext.getHttpClient().request(method, 
requestOptions, this::handleResponse)

httpClientWithContext.getHttpClient():這個方法返回的是HttpClient,上面說的HttpClient做用就體現出來了,用來初始化了咱們發送請求的關鍵先生:HttpClientRequest。那麼至此咱們發送請求的總體邏輯大概就清晰了。

總結

不管是採用RestTemplate的方式仍是採用rpc註解的方式來發送請求,其底層邏輯實際上是同樣的。即首先根據請求信息匹配到對方的服務信息,而後通過一些列的Handler處理,如限流、負載、容錯等等(這也是一個很好的擴展機制),最終會走到TransportClientHandler這個Handler,而後根據條件去初始化發送的request,通過HttpClientFilter的處理後就會委託給vertx的HttpClientRequest來真正的發出請求。

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

相關文章
相關標籤/搜索