乐趣区

关于请求:我是一个请求我是如何被发送的

摘要: 本文次要剖析应用 cse 提供的 RestTemplate 的场景,其实 cse 提供的 rpc 注解(RpcReference)的形式最初的调用逻辑和 RestTemplate 是必由之路的。

本文分享自华为云社区《我是一个申请,我该何去何从(下)》,原文作者:向昊。

上次咱们大略理解到了服务端是怎么解决申请的,那么发送申请又是个什么样的流程了?本文次要剖析应用 cse 提供的 RestTemplate 的场景,其实 cse 提供的 rpc 注解(RpcReference)的形式最初的调用逻辑和 RestTemplate 是必由之路的。

应用

应用 cse 提供的 RestTemplate 时候,是这样初始化的:

RestTemplate restTemplate = RestTemplateBuilder.create();

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

咱们能够留神到 2 个怪异的中央:

  • RestTemplate 是通过 RestTemplateBuilder.create() 来获取的,而不是用的 Spring 里提供的。
  • 申请门路结尾是 cse 而不是咱们常见的 http、https 且须要加上服务所属的利用 ID 和服务名称。

解析

依据 url 匹配 RestTemplate

首先看下 RestTemplateBuilder.create(),它返回的是 org.apache.servicecomb.provider.springmvc.reference.RestTemplateWrapper,是 cse 提供的一个包装类。

// 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。

回到应用的中央 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() 这个办法:

到这里咱们就分明了门路中的 cse:// 的作用了,就是为了应用 CseRestTemplate 来发动申请,也了解了为啥 RestTemplateWrapper 能够同时反对 cse 调用和非 cse 调用。

委托调用

从下面可知,咱们的 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 来真正的发出请求。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版