摘要:本文主要分析在cse框架下一個請求是怎麼被接受和處理的。
本文分享自華爲雲社區《我是一個請求,我該何去何從?》,原文做者:向昊。html
前置知識
cse的通訊是基於vert.x來搞的,因此咱們首先得了解下里面的幾個概念:apache
- 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. 參考:https://vertx.io/docs/apidocs/io/vertx/core/Verticle.html
因此咱們知道幹活的就是這個傢伙,它就是這個模式中的工具人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以前會進行線程切換,還有同步調用和異步調用的區別以及服務啓動時候初始化的邏輯等等。這些內容也是比較有意思,值得深挖。