写点什么

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

  • 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:251495

评论

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

昆仑数据库可定制的数据分片方案

KunlunBase昆仑数据库

国产数据库

2022第八届华为软件精英挑战赛全球总决赛圆满落幕,冠军奖金20万!

科技热闻

干货复盘 | 易观分析“出海非洲战略”专题分享

易观分析

非洲战略

Apache ShenYu 网关正式支持 Dubbo3 服务代理

阿里巴巴中间件

阿里云 开源 微服务 云原生 dubbo

“双碳”大局中再看业务合同电子化

鲸品堂

节能 提效降本 双碳

KunlunBase 查询优化(三)排序下推

KunlunBase昆仑数据库

国产数据库

关于加密通道规范,你真正用的是TLS,而非SSL

华为云开发者联盟

TLS 加密 ssl 加密通道 CA系统

昆仑分布式数据库Sequence功能及其实现机制

KunlunBase昆仑数据库

国产数据库

人人皆为开发者?不可错过的低代码发展新趋势

云智慧AIOps社区

大前端 低代码 数据可视化

netty系列之:epoll传输协议详解

程序那些事

Java Netty 程序那些事 5月月更

满满干货!手把手教你实现基于eTS的HarmonyOS分布式计算器

HarmonyOS开发者

HarmonyOS ETS

SaaS应用:企业数字化转型性价比最高的方式

小炮

手绘图解java类加载原理

华为云开发者联盟

Java 类加载 元数据 类静态

谈谈技术能力

阿里巴巴中间件

阿里云 程序员 中间件 技术思考

做了5年开源项目,我总结了以下提PR经验!

OpenHarmony开发者

OpenHarmony 开源生态

昆仑分布式数据库系统简介 之 SQL 标准兼容性和日常维护工作

KunlunBase昆仑数据库

国产数据库

为什么校招面试中“线程与进程的区别”老是被问到?我该如何回答?

宇宙之一粟

线程 进程 5月月更

数据库系统最佳实践系列 --- 使用 prepared statement

KunlunBase昆仑数据库

国产数据库

文档管理:企业进步的重要因素

小炮

文档管理

火山引擎A/B测试私有化实践

字节跳动数据平台

实验 火山引擎 私有化部署 ab测试

微擎同步粉丝不显示头像和昵称?

智伍应用

微擎 php开源

热烈庆祝“海泰密码技术融合创新中心&数据中心重启安全工程”双中心智能重启用

电子信息发烧客

网络安全 科技 科技企业

多款顶级好用的 Vue 表单设计器测评推荐,可拖拽生成表单

蒋川

Vue Element 组件 表单设计 Ant Design

《阿里云代码安全白皮书》5个维度应对3类代码安全问题

阿里云云效

云计算 阿里云 代码管理 代码托管 代码安全

成本节省 50%,10 人团队使用函数计算开发 wolai 在线文档应用

阿里巴巴中间件

阿里云 中间件 函数计算

小程序和App同时拥有?两者兼得的一种技术方案

Speedoooo

微信小程序 APP开发 小程序容器 小程序转app

MySQL-8.0 Group Replication 研究与改造汇总

KunlunBase昆仑数据库

国产数据库 MySQL 数据库

什么是显卡?GPU服务器到底有什么作用?

Finovy Cloud

gpu GPU服务器

烧录OpenHarmony 3.2(尝鲜版)步骤

离北况归

OpenHarmony OpenHarmony3.2

直播预告丨Hello HarmonyOS进阶课程第四课——ArkUI动画开发

HarmonyOS开发者

HarmonyOS arkui

IET 试水SiFL中文项目 为中国工程师“走出去”创造宝贵机遇

E科讯

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