我是一個請求,我該何去何從
摘要:本文主要分析在cse框架下一個請求是怎麼被接受和處理的。
本文分享自華為雲社區《我是一個請求,我該何去何從?》,原文作者:向昊。
前置知識
cse的通信是基於vert.x來搞的,所以我們首先得了解下裏面的幾個概念:
- Verticle:You can think of verticle instances as a bit like actors in the Actor Model. A typical verticle-based Vert.x application will be composed of many verticle instances in each Vert.x instance.參考://vertx.io/docs/apidocs/io/vertx/core/Verticle.html
所以我們知道幹活的就是這個傢伙,它就是這個模式中的工具人
- Route:可以看成是一個條件集合(可以指定url的匹配規則),它用這些條件來判斷一個http請求或失敗是否應該被路由到指定的Handler
- Router:可以看成一個核心的控制器,管理着Route
- VertxHttpDispatcher:是cse里的類,可以看成是請求分發處理器,即一個請求過來了怎麼處理都是由它來管理的。
初始化
RestServerVerticle
經過一系列流程最終會調用這個方法:
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的分析,先挖個坑,以後填上~
調用棧如下:
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)這個方法:
// 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。
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 //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之前會進行線程切換,還有同步調用和異步調用的區別以及服務啟動時候初始化的邏輯等等。這些內容也是比較有意思,值得深挖。