我是一个请求,我该何去何从

摘要:本文主要分析在cse框架下一个请求是怎么被接受和处理的。

本文分享自华为云社区《我是一个请求,我该何去何从?》,原文做者:向昊。html

前置知识

cse的通讯是基于vert.x来搞的,因此咱们首先得了解下里面的几个概念:apache

因此咱们知道干活的就是这个家伙,它就是这个模式中的工具人api

  • Route:能够当作是一个条件集合(能够指定url的匹配规则),它用这些条件来判断一个http请求或失败是否应该被路由到指定的Handler
  • Router:能够当作一个核心的控制器,管理着Route
  • VertxHttpDispatcher:是cse里的类,能够当作是请求分发处理器,即一个请求过来了怎么处理都是由它来管理的。

初始化

RestServerVerticle

通过一系列流程最终会调用这个方法:cookie

io.vertx.core.impl.DeploymentManager#doDeploy():注意若是在这个地方打断点,可能会进屡次。由于上面也提到过咱们的操做都是基于Verticle的,cse中有2种Verticle,一种是org.apache.servicecomb.foundation.vertx.client.ClientVerticle一种是org.apache.servicecomb.transport.rest.vertx.RestServerVerticle,这篇文章咱们主要分析接受请求的流程,即着眼于RestServerVerticle,至于ClientVerticle的分析,先挖个坑,之后填上~app

调用栈以下:框架

VertxHttpDispatcher

由上图可知,会调用以下方法:异步

// org.apache.servicecomb.transport.rest.vertx.RestServerVerticle#start
	public void start(Promise<Void> startPromise) throws Exception {
	    // ...
	    Router mainRouter = Router.router(vertx);
	    mountAccessLogHandler(mainRouter);
	    mountCorsHandler(mainRouter);
	    initDispatcher(mainRouter);
	    // ...
	}

在这里咱们看到了上文提到的Router,继续看initDispatcher(mainRouter)这个方法:async

// org.apache.servicecomb.transport.rest.vertx.RestServerVerticle#initDispatcher
	private void initDispatcher(Router mainRouter) {
	    List<VertxHttpDispatcher> dispatchers = SPIServiceUtils.getSortedService(VertxHttpDispatcher.class);
	    for (VertxHttpDispatcher dispatcher : dispatchers) {
	        if (dispatcher.enabled()) {
	            dispatcher.init(mainRouter);
	        }
	    }
	}

首先经过SPI方式获取全部VertxHttpDispatcher,而后循环调用其init方法,因为分析的不是边缘服务,即这里咱们没有自定义VertxHttpDispatcher。ide

Router

接着上文分析,会调用以下方法:工具

// org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher
	public void init(Router router) {
	    // cookies handler are enabled by default start from 3.8.3
	    String pattern = DynamicPropertyFactory.getInstance().getStringProperty(KEY_PATTERN, null).get();
	    if(pattern == null) {
	        router.route().handler(createBodyHandler());
	        router.route().failureHandler(this::failureHandler).handler(this::onRequest);
	    } else {
	        router.routeWithRegex(pattern).handler(createBodyHandler());
	        router.routeWithRegex(pattern).failureHandler(this::failureHandler).handler(this::onRequest);
	    }
	}

因为通常不会主动去设置servicecomb.http.dispatcher.rest.pattern这个配置,即pattern为空,因此这个时候是没有特定url的匹配规则,即会匹配全部的url

咱们须要注意handler(this::onRequest)这段代码,这个代码就是接受到请求后的处理。

处理请求

通过上面的初始化后,我们的准备工做已经准备就绪,这个时候忽然来了一个请求

GET http://127.0.0.1:18088/agdms/v1/stock-apps/query?pkgName=test ),便会触发上面提到的回调,以下:

// org.apache.servicecomb.transport.rest.vertx.VertxRestDispatcher#onRequest
	protected void onRequest(RoutingContext context) {
	    if (transport == null) {
	        transport = CseContext.getInstance().getTransportManager().findTransport(Const.RESTFUL);
	    }
	    HttpServletRequestEx requestEx = new VertxServerRequestToHttpServletRequest(context);
	    HttpServletResponseEx responseEx = new VertxServerResponseToHttpServletResponse(context.response());
	 
	    VertxRestInvocation vertxRestInvocation = new VertxRestInvocation();
	    context.put(RestConst.REST_PRODUCER_INVOCATION, vertxRestInvocation);
	    vertxRestInvocation.invoke(transport, requestEx, responseEx, httpServerFilters);
	}

最主要的就是那个invoke方法:

// org.apache.servicecomb.common.rest.RestProducerInvocation#invoke
	public void invoke(Transport transport, HttpServletRequestEx requestEx, HttpServletResponseEx responseEx,
	    List<HttpServerFilter> httpServerFilters) {
	    this.transport = transport;
	    this.requestEx = requestEx;
	    this.responseEx = responseEx;
	    this.httpServerFilters = httpServerFilters;
	    requestEx.setAttribute(RestConst.REST_REQUEST, requestEx);
	 
	    try {
	        findRestOperation();
	    } catch (InvocationException e) {
	        sendFailResponse(e);
	        return;
	    }
	    scheduleInvocation();
	}

这里看似简单,其实后背隐藏着大量的逻辑,下面来简单分析下findRestOperation()和scheduleInvocation()这2个方法。

findRestOperation

从名字咱们也能够看出这个方法主要是寻找出对应的OperationId

// org.apache.servicecomb.common.rest.RestProducerInvocation#findRestOperation    
	protected void findRestOperation() {
	      MicroserviceMeta selfMicroserviceMeta = SCBEngine.getInstance().getProducerMicroserviceMeta();
	      findRestOperation(selfMicroserviceMeta);
	  }
  • SCBEngine.getInstance().getProducerMicroserviceMeta():这个是获取该服务的一些信息,项目启动时,会将本服务的基本信息注册到注册中心上去。相关代码能够参考:org.apache.servicecomb.serviceregistry.RegistryUtils#init。

本服务信息以下:

咱们主要关注这个参数:intfSchemaMetaMgr,即咱们在契约中定义的接口,或者是代码中的Controller下的方法。

  • findRestOperation(selfMicroserviceMeta):首先经过上面的microserviceMeta获取该服务下全部对外暴露的url,而后根据请求的RequestURI和Method来获取OperationLocator,进而对restOperationMeta进行赋值,其内容以下:

能够看到这个restOperationMeta里面的内容十分丰富,和咱们接口是彻底对应的。

scheduleInvocation

如今咱们知道了请求所对应的Operation相关信息了,那么接下来就要进行调用了。可是调用前还要进行一些前置动做,好比参数的校验、流控等等。

如今选取关键代码进行分析:

  • createInvocation:这个就是建立一个Invocation,Invocation在cse中仍是一个比较重要的概念。它分为服务端和消费端,它们之间的区别仍是挺大的。建立服务端的Invocation时候它会加载服务端相关的Handler,同理消费端会加载消费端相关的Handler。此次咱们建立的是服务端的Invocation,即它会加载org.apache.servicecomb.qps.ProviderQpsFlowControlHandler、org.apache.servicecomb.qps.ProviderQpsFlowControlHandler、org.apache.servicecomb.core.handler.impl.ProducerOperationHandler这3个Handler(固然这些都是可配置的,不过最后一个是默认加载的,具体能够参考这篇文章:浅析CSE中Handler
  • runOnExecutor:这个方法超级重要,我们也详细分析下,最终调用以下:
// org.apache.servicecomb.common.rest.AbstractRestInvocation#invoke
	public void invoke() {
	    try {
	        Response response = prepareInvoke();
	        if (response != null) {
	            sendResponseQuietly(response);
	            return;
	        }
	 
	        doInvoke();
	    } catch (Throwable e) {
	        LOGGER.error("unknown rest exception.", e);
	        sendFailResponse(e);
	    }
	}
    • prepareInvoke:这个方法主要是执行HttpServerFilter里面的方法,具体能够参考:浅析CSE中的Filter执行时机。若是response不为空就直接返回了。像参数校验就是这个org.apache.servicecomb.common.rest.filter.inner.ServerRestArgsFilter的功能,通常报400 bad request就能够进去跟跟代码了
    • doInvoke:相似责任链模式,会调用上面说的3个Handler,前面2个Handler我们不详细分析了,直接看最后一个Handler,即org.apache.servicecomb.core.handler.impl.ProducerOperationHandler
// org.apache.servicecomb.core.handler.impl.ProducerOperationHandler#handle
	public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception {
	    SwaggerProducerOperation producerOperation =
	        invocation.getOperationMeta().getExtData(Const.PRODUCER_OPERATION);
	    if (producerOperation == null) {
	        asyncResp.producerFail(
	            ExceptionUtils.producerOperationNotExist(invocation.getSchemaId(),
	                invocation.getOperationName()));
	        return;
	    }
	    producerOperation.invoke(invocation, asyncResp);
	}

producerOperation是在启动流程中赋值的,具体代码能够参考:org.apache.servicecomb.core.definition.schema.ProducerSchemaFactory#getOrCreateProducerSchema,其内容以下:

能够看到,这其下内容对应的就是咱们代码中接口对应的方法。

接着会调用org.apache.servicecomb.swagger.engine.SwaggerProducerOperation#invoke方法:

// org.apache.servicecomb.swagger.engine.SwaggerProducerOperation#invoke
	public void invoke(SwaggerInvocation invocation, AsyncResponse asyncResp) {
	    if (CompletableFuture.class.equals(producerMethod.getReturnType())) {
	        completableFutureInvoke(invocation, asyncResp);
	        return;
	    }
	 
	    syncInvoke(invocation, asyncResp);
	}

因为咱们的同步调用,即直接看syncInvoke方法便可:

public void syncInvoke(SwaggerInvocation invocation, AsyncResponse asyncResp) {
	    ContextUtils.setInvocationContext(invocation);
	    Response response = doInvoke(invocation);
	    ContextUtils.removeInvocationContext();
	    asyncResp.handle(response);
	}

我们通常上下文传递信息就是这行代码"搞的鬼":ContextUtils.setInvocationContext(invocation),而后再看doInvoke方法:

public Response doInvoke(SwaggerInvocation invocation) {
	    Response response = null;
	    try {
	        invocation.onBusinessMethodStart();
	 
	        Object[] args = argumentsMapper.toProducerArgs(invocation);
	        for (ProducerInvokeExtension producerInvokeExtension : producerInvokeExtenstionList) {
	            producerInvokeExtension.beforeMethodInvoke(invocation, this, args);
	        }
	 
	        Object result = producerMethod.invoke(producerInstance, args);
	        response = responseMapper.mapResponse(invocation.getStatus(), result);
	 
	        invocation.onBusinessMethodFinish();
	        invocation.onBusinessFinish();
	    } catch (Throwable e) {
	        if (shouldPrintErrorLog(e)){
	            LOGGER.error("unexpected error operation={}, message={}",
	                invocation.getInvocationQualifiedName(), e.getMessage());
	        }
	        invocation.onBusinessMethodFinish();
	        invocation.onBusinessFinish();
	        response = processException(invocation, e);
	    }
	    return response;
	}
      • producerInvokeExtenstionList:根据SPI加载ProducerInvokeExtension相关类,系统会自动加载org.apache.servicecomb.swagger.invocation.validator.ParameterValidator,顾名思义这个就是校验请求参数的。如校验@Notnull、@Max(50)这些标签。
      • producerMethod.invoke(producerInstance, args):经过反射去调用到具体的方法上!

这样整个流程差很少完结了,剩下的就是响应转换和返回响应信息。

总结

这样咱们大概了解到了咱们的服务是怎么接受和处理请求的,即请求进入咱们服务后,首先会获取服务信息,而后根据请求的路径和方法去匹配具体的接口,而后通过Handler和Filter的处理,再经过反射调用到咱们的业务代码上,最后返回响应。

总体流程看似简单可是背后隐藏了大量的逻辑,本文也是摘取相对重要的流程进行分析,还有不少地方没有分析到的,好比在调用runOnExecutor以前会进行线程切换,还有同步调用和异步调用的区别以及服务启动时候初始化的逻辑等等。这些内容也是比较有意思,值得深挖。

 

点击关注,第一时间了解华为云新鲜技术~

相关文章
相关标签/搜索