一, 前言
宜信开源 | 调用链系列(1): 解读 UAVStack 中的贪吃蛇
上篇文章分享了一下调用链的模型设计及模型时序图. 相信大家通过上一篇文章对调用链有了一个整体上的了解, 如: 调用链是什么, 能做什么及整体实现策略.
这篇文章我们继续介绍调用链的服务端信息收集以及服务间上下文传递.
二, 服务端信息收集
服务端信息收集整体流程如下图所示, 通过在应用容器 (tomcat 等) 启动过程中植入切点从而实现在应用逻辑执行之前和之后对请求进行劫持.
应用逻辑执行之前: 解析 request 中调用链信息, 并初始化调用链上下文;
应用逻辑执行之后: 解析 response 中调用链信息, 并将本次请求处理的所有调用链信息输出到日志文件.
三, 切点植入
在介绍切点之前我们应该对 servlet 容器 (本文以 tomcat 为例) 处理一次请求的大致流程有一个整体的了解.
图片来源于网络
在 Connector 接收到一次连接并转化成请求 (Request) 后, 会将请求传递到 Engine 的管道 (Pipeline) 的阀 (ValveA) 中. 请求在 Engine 的管道中会传递到 Engine Valve 这个阀中. 接着请求会从 Engine Valve 传递到一个 Host 的管道中, 在该管道中传递到 Host Valve 这个阀里. 接着从 Host Valve 传递到一个 Context 的管道中, 在该管道中传递到 Context Valve 中. 接下来请求会传递到 Wrapper C 内的管道所包含的阀 Wrapper Valve 中, 在这里会经过一个过滤器链(Filter Chain), 最终送到一个 Servlet 中. 借助于 tomcat 的这种架构设计, 我们可以通过在 tomcat 处理一次请求的生命周期过程中植入自己的逻辑, 将 tomcat 对外提供的能力进行一次增强, 即 UAV 的中间件增强技术.
中间件增强技术除了巧妙运用了 tomcat 容器的架构设计之外还借助了 java Instrumentation(它给我们提供了一种能够在对象第一次加载时动态修改字节码的能力, 由于篇幅原因在此不进行详细讲解, 不明白的小伙伴自行查阅资料). 在 UAV 中通过 UAVServer 对外提供各种切点能力.
有了中间件增强技术, 在应用逻辑执行之前和之后的切点就有了, 接下来就是在这些切点位置执行我们自己的调用链逻辑了.
四, 中间件增强技术在调用链中的使用
上文介绍的间件增强技术是一种通过使用 javaagent 方式动态地在 tomcat 代码中植入切点代码并以 UAVServer 的形式对外提供能力的框架(具体能力后续文章会详细介绍). 轻调用链实现正是使用了 UAVServer 对外提供的 GlobalFilterHandler 能力.
GlobalFilterHandler: 这里的 GlobalFilterHandler 是中间件增强技术中的一种能力, 与传统的 filter 没有任何关系. 它对外提供了四个能力:
doRequest: 在所有应用处理请求之前进行劫持;
doResponse: 在所有应用处理请求之后进行劫持;
BlockHandlerChain: 阻塞自当前 handler 以后的所有 handler, 此处的 handler 为注册在当前;
BlockFilterChain 阻塞自当前 Filter 以后的所有 Filter.
调用链借助于 GlobalFilterHandler 提供的前两个能力, 实现了在应用处理请求之前和之后执行调用链逻辑的功能.
五, 轻调用链实现
具体 UML 图如下:
从 UML 图中可以清晰地看到, InvokeChainSupporter(调用链实现逻辑入口和调用链所需资源初始化实现类)将中间件增强技术进行了二次增强. 它允许使用者在其中注册不同的 handler, 并且在 handler 的 preCap 和 doCap(中间件增强技术中的逻辑执行之前和之后的切点术语)方法之前和之后动态织入 adapter, 从而能够执行更多的定制化适配和个性化逻辑. 所有 supporter 和 adapter 均采用反射调用方式, 最大程度上减少了中间件增强技术的依赖.
有了二次增强技术, 我们就可以开始下面的调用链绘制工作了.
轻调用链绘制实现主要依赖于注册在 InvokeChainSupporter 上的 ServiceSpanInvokeChainHandler. 主要绘制过程如下:
解析请求信息, 提取其中调用链关心的信息, 并将解析出来的信息放入上下文中;
通过解析出来的请求头信息进行逻辑分流, 根据不同的协议类型就行不同的逻辑处理; mq 逻辑 http 逻辑 dubbo 逻辑
初始化调用链上下文, 并初始化 main span 上下文;
在应用处理完请求之后, 将调用链信息进行统一输出.
下面来看一下具体每一步都做了什么.
5.1 解析请求信息
对于像 tomcat 这类中间件容器, 所有进入 tomcat 的请求都会被封装成 HttpServletRequest 和 HttpServletResponse(后面简称 request 和 response)最终进入用户的 servlet 中. 调用链借助于中间件增强技术会在用户逻辑处理之前将 request 和 response 进行一次拦截, 并解析其中是否含有调用链信息. 如果有则将调用链信息进行封装放入上下文中.
5.2 逻辑分流
由于不同协议对应的调用链绘制逻辑也不同, 此处调用链会根据协议类型进行一次分发.
5.3 初始化调用链上下文
将调用链上下文中的信息进行解析:
没有父节点则将当前节点当作初始化节点, 并初始化记录当前服务内调用链信息的 main span;
有父节点则根据父节点信息初始化当前节点, 并初始化记录当前服务内调用链信息的 main span.
main span: 在服务内可能会进行多次客户端通讯或服务间通讯, 需要一个 main span 来记录当前服务内调用链最后一个节点的信息.
5.4 调用链信息输出
在用户逻辑处理结束之后, 调用链记录器会从上下文中取出当前服务的调用链信息并将其输出到指定日志路径.
5.5 服务间上下文传递
对于不同协议调用链传递信息方式也略有不同, 具体实现方式借助了中间件增强技术提供的另一个能力: AppFrkHook(简称 hook, 此功能在客户端调用链实现时会进行具体介绍). 它能够对用户使用的客户端技术进行劫持, 如用户使用了 httpclient 进行通讯, 则对 httpclient 进行劫持并动态织入代码, 从而达到在 http 通讯的过程中注入调用链上下文信息的效果. 目标服务在解析请求信息时, 将调用链上下文进行解析; 在初始化调用链上下文逻辑时, 使用传递过来的信息初始化目标服务的调用链上下文, 实现跨系统调用时调用链连接.
六, 总结
读完本文之后读者应该对中间件增强技术的实现有了一个大概的了解, 并且对其提供的 GlobalFilterHandler 能力有了一定的认识. 对于调用链应明白了服务端和服务间调用链的绘制全过程.
来源: https://www.cnblogs.com/yixinjishu/p/11351055.html