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

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

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

前言

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

  • 请求处理前到请求处理后的上下文传递;
  • 各个客户端调用间的上下文传递;
  • 各个服务间调用时的上下文传递。

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

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

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

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

1、在同一线程内传递

2、跨线程池传递

3、异步线程传递

4、跨应用传递

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

TransmittableThreadLocal<String> parent =newTransmittableThreadLocal<String>();parent.set("value-set-in-parent");Runnable task =new Task("1");// 额外的处理,生成修饰了的对象ttlRunnableRunnable 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()方法,该方法声明如下:

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

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

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

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

4.跨应用上下文传递

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

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

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

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

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

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

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

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

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

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

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

总结

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

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

作者:朱文强 宜信技术学院

调用链系列四:调用链上下文传递相关推荐

  1. 【集合论】序关系 ( 链 | 反链 | 链与反链示例 | 链与反链定理 | 链与反链推论 | 良序关系 )

    文章目录 一.链 二.反链 三.链与反链示例 四.链与反链定理 五.链与反链推论 六.链与反链推论示例 七.良序关系 一.链 <A,≼><A, \preccurlyeq>< ...

  2. request中的内容存储_宜信开源|调用链系列(3):解读UAVStack中的调用链技术...

    拓展阅读:宜信开源|调用链系列(1):解读UAVStack中的贪吃蛇 调用链系列(二):解读UAVStack中的贪吃蛇-调用链 在Java中,HTTP协议的请求/响应模型是由Servlet规范+Ser ...

  3. Android调用WebService系列之对象构建传递

    上一篇我们讲了如何封装Android调用WebService的能力,把上一章的类加入我们便有了与WebService通讯的能力.往往我们会遇到WebService调用是通过对象来进行实际交互调用的.于 ...

  4. 新浪微相册https外链图片无法调用解决方法

    [问题现象] 因为外链不能直接调用在其他网站了.不过http的图片倒是没问题,所有https也就是证书形式调用的网址图片都无法正常在第三方网站显示.想要看图必须单独访问以下https的新浪图片,在回到 ...

  5. java 过滤器调用链_FilterChain(过滤器链)详解

    在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以针对某一个 URL 进行拦截.如果多个 Filter 程序都对同一个 URL 进行拦截,那么这些 Filter ...

  6. (四)我的JavaScript系列:原型链

    夜深风竹敲秋韵,万叶千声皆是恨. 原型链对于JavaScript来说是个很核心的概念.JavaScript不是基于类模板的面向对象语言:反而,它的面向对象机制是基于原型的.我们不可能说某个对象属于什么 ...

  7. 区块链教程(四):搭建私链、web3.js基础

    注:本教程为技术教程,不谈论且不涉及炒作任何数字货币 区块连教程(一):前置知识-linux补充 区块链教程(二):基础概念介绍 区块链教程(三):Solidity编程基础 区块链教程(四):搭建私链 ...

  8. 基于Java语言构建区块链(四)—— 交易(UTXO)

    基于Java语言构建区块链(四)-- 交易(UTXO) 2018年03月11日 00:48:01 wangwei_hz 阅读数:909 标签: 区块链比特币 更多 个人分类: 区块链 文章的主要思想和 ...

  9. IPFS + 区块链 系列】 入门篇 - IPFS + Ethereum (上篇)-js-ipfs-api

    目录 1. 内容简介 2. IPFS-HTTP效果图 3. 实现步骤 3.1 安装create-react-app 3.2 React项目创建 3.3 运行React项目 3.4 浏览项目 3.5 安 ...

最新文章

  1. WPF 回车转Tab实现跳转
  2. 第二种PHP协议,PHP多种形式,第二种使用来自First的数据
  3. day16-小数据池
  4. Fashion-MNIST 一周年 | Google NIPS最爱,还登上了Science
  5. SAP 电商云 Spartacus UI 的 slot,position 和 Component
  6. 句子中单词首字母大写转换
  7. mysql忽略数据类型_MYSQL 常用数据类型
  8. webpack 异步加载配置文件_详解webpack异步加载业务模块
  9. 最好用的两个oracle数据库客户端(OB11+osqledit)
  10. python中head是什么意思_python爬虫中header是什么?怎么用?
  11. Zabbix 2.2 安装图解教程
  12. JDK包括的Java基础类库_问:JDK是Java平台的核心,Java运行环境、Java工具、Java基础类库(rt.jar)。J...
  13. 测绘与设计之间的鸿沟:坐标系,教你如何将CAD与测绘数据准确叠加
  14. Photoshop无缝背景制作
  15. 《大数据原理:复杂信息的准备、共享和分析》一一1.3 自动编码 格物致知。...
  16. DVB-S2 DVB-S2X DVB-DSNG 发射机 接收机FPGA IP
  17. 解决win10下samba不能访问
  18. 日语词汇辨析:以来と以降と以後
  19. 第一届大数据技术创新与创业大赛
  20. 微信小程序点击换头像-图片从本地获取-tab选项卡-点击按钮出弹框

热门文章

  1. 《Android App开发入门:使用Android Studio 2.X开发环境》——1-3 Android Studio 快速上手...
  2. sql server 经典SQL——分组统计
  3. Spring PropertyPlaceholderConfigurer
  4. Web设计和开发人员有用的15Chrome插件
  5. modelsim的destbench模型1
  6. linux perl telnet安装,linux @ Net :: Telnet和vt-100终端的Perl问题
  7. 【python教程入门学习】学习Python可以做什么
  8. PHP遍历用blade标签表示,php-Laravel Blade {{$variable或’Default Text’}}无法使用网址
  9. linux中文件记录的时间参数,【Linux】stat命令查看文件的三个时间参数
  10. cx_oracle写日志信息_日志系统的设计