摘要:本文次要剖析应用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.TransportClientHandlerpublic 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来真正的发出请求。
点击关注,第一工夫理解华为云陈腐技术~