写点什么

调用链系列四:调用链上下文传递

  • 2020-02-12
  • 本文字数:3328 字

    阅读完需:约 11 分钟

调用链系列四:调用链上下文传递

在之前的调用链系列文章中,我们已经对调用链进行了详细介绍,相信大家已经对调用链技术有了基本的了解。


其实,在调用链的绘制过程中,调用链上下文的传递非常值得关注。各个节点在获取上层上下文后生成新的上下文并向后传递。在传递过程中,上下文一旦丢失或出现异常就会导致调用链数据缺失,甚至可能会发生断裂。


本文主要讲述 UAV 中调用链上下文传递过程中的部分实现细节。




前言


在调用链的实现中,主要存在以下几种调用链上下文的传递方式:


  • 请求处理前到请求处理后的上下文传递;

  • 各个客户端调用间的上下文传递;

  • 各个服务间调用时的上下文传递。


在这三种情况中,上下文传递过程中所传递的信息以及遇到的问题会有所不同。


  • 在请求处理前后的上下文传递过程中,需要传递的信息一般包括 traceID、 spanID、请求开始的时间以及部分请求参数等。相关代码可能会因为异步执行导致上下文面临异步线程传递的问题。

  • 在客户端调用间及服务间调用中,需要传递的上下文信息一般只包括 traceID 和 spanID。但客户端调用之间的上下文传递可能会遇到跨线程池传递的问题,服务间调用则存在跨应用传递的问题。


因此,我们把今天所讲的上下文传递划分为以下四种场景进行分析:


1、在同一线程内传递


2、跨线程池传递


3、异步线程传递


4、跨应用传递


为了更好地阐述这四种场景,我们假设存在以下业务调用过程:


1550129281876017593.png


假设某次请求首先进入服务 A,在服务 A 的业务代码中发起了一次 JDBC 请求,访问了一次数据源;然后又通过 httpClient(同步,异步)发起了一次 http 访问并返回相应结果。


数字表示所在点存在调用链上下文信息的获取。在大多数的相邻点之间都会涉及到调用链上下文的传递。


例如,从 2 点到 3 点就是请求前和请求后的上下文传递,从 3 点到 4 点就是两次客户端调用间的上下文传递,从 4 点到 5 点就是服务间的上下文传递。下面我们将在不同的场景下说明各点之间的上下文传递过程。

1.在同一线程内的上下文传递

这种场景比较常见,也是最简单的场景。


假设上述模拟流程中全部为同步操作,业务代码中不涉及任何的线程池(数据库连接池不影响)及异步操作,那么服务 A 中调用链的相关代码均会在同一个线程中执行。


说到这里,想必大家都会想到使用 ThreadLocal 便可以解决。使用 ThreadLocal 的确可以解决同线程中的参数共享传递问题。在 UAV 中,一般两次客户端调用之间的上下文传递都直接使用 ThreadLocal(其实并不是原生的 ThreadLocal,后文会有所介绍),传递过程如下:


1550129297901025915.jpg


但是很多时候,业务代码中经常会涉及到异步或者提交线程池的操作,此时单单使用 ThreadLocal 便无法满足相应的需求。下面我们就来讨论有关含有线程池操作和异步请求的上下文传递问题。



2.跨线程池的上下文传递

首次我们来看一下跨线程池上下文传递问题。


假设上述的业务场景中在进行 JDBC 操作时,当前线程仅负责将 JDBC 操作提交到线程池中,那么此时上下文信息从 1 点传递到 2 点就会遇到跨线程池的问题,此时使用 ThreadLocal 无法上下文信息的传递。


当然有的同学可能会说用 InheritableThreadLocal。但是提交线程和线程池线程本身并不存在父子关系,因此 InheritableThreadLocal 也是无法完成跨线程池的上下文传递的。


为了解决这个问题,我们使用了阿里开源的跨线程池的 ThreadLocal 组件:transmittable-thread-local(以下简称 TTL,具体的实现方式有兴趣的同学可以去了解下https://github.com/alibaba/transmittable-thread-local)


通过该组件可以增强 ThreadLocal 的功能实现跨线程池的传递。以下是 github 中 TTL 的使用示例:


TransmittableThreadLocal


parent.set(“value-set-in-parent”);


Runnable task =new Task(“1”);


// 额外的处理,生成修饰了的对象 ttlRunnable


Runnable ttlRunnable = TtlRunnable.get(task);


executorService.submit(ttlRunnable);


// Task 中可以读取,值是"value-set-in-parent"


String value = parent.get();


可以看到,想要 TTL 起作用,就需要将业务代码中的 runnable 更换为 TtlRunnable。为了实现对业务代码的零入侵,我们借助 javaagent 机制增加了一个针对 ThreadPoolExecutor 等一些 Eexecutor 的 ClassFileTransformer,将提交到线程池中的 Runnable 和 Callable 包装成相应的 TtlRunnable 和 TtlCallable,这样就实现了在不修改业务代码的情况下完成跨线程池的上下文传递。


另外,由于 TTL 具备 ThreadLocal 的所有特性,因此 UAV 的上下文传递过程中用到的 ThreadLocal 均是 TTL。

3.异步线程中上下文传递

看完上面的跨线程池操作,我们再来看一下异步线程的问题。


假设在上述模拟场景中,我们使用异步 HttpClient 发送了一个异步的 Http 请求。由于是异步操作,4 点的代码和 7 点的代码(这里 7 点的上下文是从 4 点中获取的属于请求前后的上下文获取场景)实际上会在不同的线程中执行,导致 7 点无法获取 4 点放入 ThreadLocal 中的上下文数据,进而导致调用链的数据丢失。


为了解决这个问题,在 UAV 中我们同时使用了字节码改写和动态代理技术。关键在于目标劫持函数的选择,需要能够获取到异步线程的回调对象。


下面以异步 HttpClient 为例介绍 UAV 中异步线程上下文的传递过程。


在异步 HttpClient 中,我们劫持的是 InternalHttpAsyncClient 类的 execute()方法,该方法声明如下:


1550129319021009666.png


一般情况下,异步的使用方式为传入一个 callback 接口对象,在 callback 中实现相应的异步逻辑;或者使用返回的 Future 接口对象的 get()方法实现一种异步转同步的操作。


为了能够在相应的地方获取到调用链的上下文,我们首先通过改写字节码的方式,在方法执行前生成调用链的上下文信息;然后对 FutureCallback 接口做动态代理,同时将生成的上下文信息传入到代理对象中,并替换原来的 callback 对象。


这样当异步请求返回调用 callback 接口时,实际上拿到的是我们的代理对象,此时也就完成了异步线程中上下文的传递过程,具体过程如下:


1550129333027072293.jpg


为了支持通过 get()方法的异步转同步操作,在这里我们也对返回的 Future 接口做了动态代理来完成上下文的传递。

4.跨应用上下文传递

说完应用内的上下文传递过程,我们来看一下跨应用的上下文传递问题。


跨应用的场景也是比较常见的。在这种场景下,上下文传递的思路一般是将上下文的信息按照一定的协议反序列化,然后放入到请求的传输报文中;在下游服务中劫持请求,获取其中的信息完成上下文的传递。在整个处理过程中,不对应用报文解析造成任何影响。


常见的传输协议中如 HTTP 协议,Dubbo 的 RPC 协议,RocketMQ 的 MQ 协议等。这些协议一般会含有类似于头信息的结构,用于表示本次请求的额外信息。


我们恰好可以利用这一点,将上下文信息放入其中传输给下游服务,完成上下文的传递。


下面我们仍然以异步 HttpClient 来介绍 UAV 跨应用上下文的传递过程。


之前我们说过,在异步 HttpClient 中,我们劫持的是 execute()方法。在这个方法中,我们可以拿到 HttpAsyncRequestProducer 接口对象,具体接口如下:


1550129417726034155.png


通过其中的 generateRequest()方法,我们就可以拿到本次请求将要发送的 request 对象,利用 request 的 setHeader()方法,将调用链的上下文信息放入 Header 中传入下游。


这里的上下文一般比较简单,基本上都是由 traceID 和 spanID 的字符串构成,传输成本也不高。


至于下游服务中如何解析该上下文,实际上之前的调用链系列中有谈到,就是借助 UAV 的中间件增强框架(MOF),在服务端劫持请求对应的 request 对象,然后直接从其头信息中获取即可。


1550129392978049116.jpg


其他的 RPC 或者 MQ 等协议,在 UAV 中均是采用这种方式完成,只是具体的 API 和劫持点有所不同。


例如,Dubbo 远程调用过程中使用是其中的 RpcContext,而 RocketMQ 则是放入到了 msg 的 UserProperty 中。感兴趣的同学可以到 UAVStack(https://github.com/uavorg/uavstack)中查看相关的源码。


总结


了解这些上下文的传递过程后,大家便可以基于调用链实现更为强大的功能。UAV 中,调用链和日志关联功能就是通过劫持日志输入部分的相关代码,获取调用链上下文,然后将 traceID 输出到业务日志中来实现的。


大家也可以自己在业务代码中尝试获取调用链的上下文,将业务数据与调用链数据打通,方便数据统计和问题排查。


本文转载自宜信技术学院网站。


原文链接:http://college.creditease.cn/detail/221


2020-02-12 15:251432

评论

发布
暂无评论
发现更多内容

游戏直播成品源码究竟是什么?开发搭建平台优缺点有哪些?

软件开发-梦幻运营部

Partisia Blockchain 质押 APR 教程一览,可以随时取消

西柚子

字节跳动豆包大模型发布,火山引擎全栈 AI 服务助力企业智能化转型

新消费日报

谱写云智融合新篇章,天翼AI云电脑引领产业变革!

编程猫

软件测试学习笔记丨Pytest编写插件—为第三方插件添加命令行参数

测试人

软件测试 自动化测试 测试开发 pytest

客户案例|博睿数据助力上海证券App实现业务增长

博睿数据

全新征程,Pencils Protocol 再获 OKX Ventures 投资

加密眼界

当实时互动遇上新硬件:GIAC 全球互联网架构大会「新硬件」专题论坛

声网

容器内存可观测性新视角:WorkingSet 与 PageCache 监控

阿里巴巴云原生

阿里云 云原生 可观测

全新征程,Pencils Protocol 再获 OKX Ventures 投资

大瞿科技

通义千问 2.5 “客串” ChatGPT4,你分的清吗?

阿里巴巴云原生

阿里云 云原生

云3D渲染在汽车虚拟仿真中的优势体现

3DCAT实时渲染

实时云渲染 云3D渲染 汽车虚拟仿真

美团面试:如何实现线程任务编排?

王磊

Java

德国 Industrial AI Podcast 聚焦 Apache IoTDB:探索使用 AINode 进行时序数据管理

Apache IoTDB

地理数据可视化的神奇组合:Python和Geopandas

快乐非自愿限量之名

Python 数据可视化 开发语言 信息可视化

Bonree ONE 技术带动「新质」可观测

博睿数据

XEOS 对象存储深度结合 Alluxio 分布式缓存系统, GPU 利用率提高至 90% 以上

XSKY星辰天合

AI 数据基础设施

What's new in PikiwiDB(Pika) v3.5.4

apache/dubbo-go

redis Redis 可视化工具

5月21日相聚上海张江!与文心大模型一起共建大模型产业应用生态圈

飞桨PaddlePaddle

百度 飞桨 大模型 文心一言 文心中国行

线上展厅怎么做?如何打造一个成功的线上展厅?

点量实时云渲染

云渲染 实时云渲染 3D实时云渲染 线上展厅 数字展厅

碳课堂|ISO 14064-3 温室气体核查规范与指南

AMT企源

双碳 碳管理 ISO 14064

不能忽视的数据治理

凌晞

数据治理

事业-最佳实践-架构-概要设计模版

南山

架构设计 技术方案 概要设计 技术方案模版 概要设计模版

字节跳动打响大模型价格战;苹果将在 iPhone、iPad 上推出眼球追踪功能丨 RTE 开发者日报 Vol.205

声网

一键自动化博客发布工具,用过的人都说好(掘金篇)

程序那些事

工具 程序那些事 自动发布

矩阵起源全面拥抱AIDC开源软件生态,世纪互联领投千万美元Pre A轮融资

MatrixOrigin

事业-最佳实践-架构-架构边界争议决策

南山

架构 架构决策 架构边界

VMware 网络连接的几种方式

玄兴梦影

蜗牛游戏宣布2024年第一季度财报业绩

财见

微创软件荣获"SSCL金链奖----优秀数字化转型奖"

财见

超前预热|博睿数据将应邀出席双态IT用户大会,分享《构建云原生时代的一体化智能可观测性》

博睿数据

调用链系列四:调用链上下文传递_区块链_朱文强_InfoQ精选文章