摘要:本文主要分析在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.参考:https://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#startpublic 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#initDispatcherprivate 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.VertxRestDispatcherpublic void init(Router router) {// cookies handler are enabled by default start from 3.8.3String 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#onRequestprotected 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#invokepublic 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#invokepublic 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#handlepublic 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#invokepublic 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之前会进行线程切换,还有同步调用和异步调用的区别以及服务启动时候初始化的逻辑等等。这些内容也是比较有意思,值得深挖。

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

我是一个请求,我该何去何从相关推荐

  1. 我是一个请求,我是如何被发送的?

    摘要:本文主要分析使用cse提供的RestTemplate的场景,其实cse提供的rpc注解(RpcReference)的方式最后的调用逻辑和RestTemplate是殊途同归的. 本文分享自华为云社 ...

  2. 我是一个秒杀请求,正在逃离这颗星球...

    作者 | 悟空聊架构 来源 | 悟空聊架构(ID:PassJava666) 星球简介 地点:β-410 星系,A-731电商星球. 时间:新纪元 2036 年. 星球简介: 中文名:A-731电商星球 ...

  3. 一个让人感伤又温馨的计算机故事:我是一个硬盘

    先是在一个转载基本不给出处网站上看到了本文的硬盘和内存部分,觉得挺好,于是按原文标题在 Google 中查找来源,发现 2007 年有篇个人博客中有更全的内容.搭档提示说,文章时代可能会更久远,于是多 ...

  4. 我是一个CPU:这个世界慢!死!了!

    女主宣言 最近小编看到一篇十分有意思的文章,多方位.无死角的讲解了CPU关于处理速度的理解,看完之后真是豁然开朗.IOT时代,随着科技的发展CPU芯片的处理能力越来越强,强大的程度已经超乎了我们的想象 ...

  5. 我是一个*** (二)

    计算机屏幕上的数字还在不停的跳动,我的运气总是不好,这辈子中过最大的奖就是安慰奖.所以,我总是没有碰到过测试几分钟,密码就跳出来的情况.现在我都习惯了.电视太难看,中央某台正在作一个节目,专门介绍他们 ...

  6. 黑客日记:我是一个黑客

    这几天眼睛要好受些了,因为刚买了一个15'的液晶显示器.也许你会觉得我以前的那个特丽珑的17"的显示器应该很威风才对.呵呵,对一个整天15个小时以上坐在显示器面前,距离不超过30厘米的人来说 ...

  7. 如何判断一个请求是否是Ajax异步请求

    前言 今天在看项目过程中,发现了一段代码.是用来判断一个请求是否是ajax请求,出于好奇,我研究了一番. 代码预览 /*** 是否是Ajax异步请求* * @param request*/public ...

  8. 我是一个流氓软件线程

    前情回顾: 我是一个explorer的线程 我是一个杀毒软件线程 我是一个IE浏览器线程 Hello, World! 我是一个流氓软件线程,我不像那些病毒和木马,我只是通过人类的电脑赚一点小钱,并不会 ...

  9. 我是一个Dubbo数据包...

    Hollis的新书限时折扣中,一本深入讲解Java基础的干货笔记! 今天给大家带来一篇关于Dubbo IO交互的文章,本文是一位同事所写,用有趣的文字把枯燥的知识点写出来,通俗易懂,非常有意思,所以迫 ...

最新文章

  1. 2018-3-28 基本粒子群优化算法
  2. HDU1824 2-sat
  3. jsp java servlet_jsp+java ,servlet如何实现用户登录和注册页面
  4. jquery动态改变div宽度和高度
  5. 2018北语c语言程序2答案,北语21春《JAVA语言程序设计》作业2题目【标准答案】...
  6. 语法和c区别_【20200925】Python基本语法
  7. 用VSCode写IEEE论文
  8. 书蠹诗魔——张岱《湖心亭看雪》
  9. 【AR优秀开源项目】ARCore项目工程汇总
  10. html2canvas加上canvas2image保存网页为图片
  11. linux 怎么格式化u盘写保护,u盘写保护怎么去掉
  12. python实现全自动百词斩单词对战,躺着上分轻轻松松
  13. 有哪些国外便宜虚拟主机适合个人建站呢
  14. 一无所知学编程:Jargon File(1)
  15. vertica MySQL_Vertica数据库 安装 | 学步园
  16. 个人永久性免费-Excel催化剂功能第21波-Excel与Sqlserver零门槛交互-执行SQL语句篇...
  17. 10个自动化测试框架,测试工程师用起来
  18. 程序员代码面试指南刷题--第五章.字符串的调整II
  19. html用divagt;做个按钮,Diva验证工具使用说明:
  20. 生活哲理故事系列之六(转贴)

热门文章

  1. 太强大了 | 一键生成,太强大了……
  2. Bootstrap 弹出提示插件Popover 的选项
  3. CSS3 动画关键帧 @keyframes
  4. c语言程序设计 猜数字,C语言程序设计(猜数字游戏)报告.doc
  5. Node.js与网络:Node.js对TCP、UDP、Socket、HTTP等协议的实现和支持
  6. 光驱怎么挂载第二个光驱_电脑光驱经常自己打开自己关闭,怎么回事
  7. html5 动态3d箭头,HTML5旋转的3D镐 | 箭头
  8. java之struts2的action的创建方式
  9. 20181030-4 每周例行报告
  10. 浅谈_依赖注入 asp.net core