Pinpoint--基础--04--请求追踪和字节码插装

2023-11-16

Pinpoint–基础–04–请求追踪和字节码插装


备注、背景

英文原文:https://naver.github.io/pinpoint/1.8.4/techdetail.html

Dapper原文 :https://ai.google/research/pubs/pub36356
说明:【】中内容为方便解释自己加的

1、分布式事务追踪,基于Google的Dapper

  1. 基于Google的Dapper,Pinpoint可以追踪到一个事务(transaction)中的分布式请求【这里说白了即指Pinpoint能够追踪到从应用A到应用B的一次分布式请求中的所有数据】

1.1、Google Dapper的 分布式事务追踪 是如何工作的

假设 分布式系统中的两个节点(Node1,Node2),当有一条消息从Node1发送到Node2时,分布式追踪系统能够识别Node1和Node2之间的关系

在这里插入图片描述

1.2、分布式系统中的消息关系

1.2.1、问题

问题在于没有方法能够识别消息之间的关系,比如我们无法辨别从Node1发送的N条消息和Node2接收到的N条消息之间的关系,换句话说,当从Node1发送第X条消息时,你无法在Node2收到的N条消息里分辨出Node1发送的第X条消息。

有系统尝试在操作系统或者TCP级别追踪这些消息,然而实现复杂度高,同时性能低,因为需要针对每个协议单独实现。此外,很难准确地跟踪消息。

1.2.2、解决

Google的Dapper提供了一种解决上述问题的简单方法。方法便是当发送消息的时候,在应用级别给这些消息加上标签,从而能够使这些消息关联起来。

例如,对于Http请求,它在Http请求头中加入标签信息,并使用这个标签信息追踪消息。

2、PinPoint

  1. Pinpoint 基于Google的Dapper中的追踪技术,同时进行了修改,在远程调用的时候,pinpoint会在调用方 加入应用级别的标签数据 来追踪分布式事务(请求)
  2. 标签数据由一系列的key组成,在pinpoint中定义为 TraceId

2.1、数据结构

2.1.1、Span

  1. RPC(远程过程调用)追踪的基本单元
  2. 代表了远程调用到达时处理的工作并且包含了追踪数据【直白点说,你可以把一个应用当作一个Span】,为了确保代码级别的可见性,Span有子节点,用SpanEvent【这个就相当于一次方法调用,故一个Span可能大部分都会有多个SpanEvent】表示,每个Span都包含一个TraceId,每个Span有一个SpanId和ParentSpanId,如果这个Span为请求的最原始的发起者,ParentSpanId为-1。

2.1.2、Trace

  1. Span的集合,由关联的RPC调用组成【这个也就是说在分布式系统中,每个应用代表一个Span,不同应用的一次全链路的请求表示一个Trace,即一个Trace会包含多个Span】
  2. 在同一个链路中的Spans共享同一个TransactionId
  3. Trace通过SpanIds和ParentSpanIds排序成分层树结构

2.1.3、TraceId

  1. 一系列key的集合,这些key包括

    1. TransactionId
    2. SpanId
    3. ParentSpanId
  2. TransactionId

    1. 代表消息id,来自单个事务的分布式系统发送、接收的消息id
    2. 这个id在一次追踪过程中唯一
  3. SpanId和ParentSpanId代表远程调用的父子关系

2.1.4、TransactionId(TxId)

一次事务(请求)中分布式系统发送/接收的消息id,在整个请求关联的所有应用服务中必须唯一

2.1.5、SpanId

处理接收RPC消息的应用id,在RPC消息到达某个节点时生成

2.1.6、ParentSpanId(pSpanId)

发起RPC调用的父span的 SpandId,如果某个节点是整个事务请求的发起者,那么它没有父span,对于这种情况,我们使用 -1表示这是整个事务请求的根span

2.2、TraceId如何工作?

2.2.1、图示

下图展示的是TraceId的行为,里面包括了三次RPC请求以及4个节点【这个就相当于一次请求,即一次transaction】

在这里插入图片描述

2.2.2、TraceId行为样例

在上图中,TransactionId(TxId)表示的是三次不同的调用通过TransactionId(TxId)关联在一起作为一次事务请求(transaction)

然而,TransactionId自身并不能明显的描述RPC之间的关系。为了识别RPC之间的关系,我们需要SpanId以及ParentSpanId(pSpanId)。

假设节点是tomcat,你可以想象成 SpanId是处理http请求的线程,parentSpanId代表的是发起这次RPC请求的SpanId【这里假设应用A和B,A调B,A和B都是一个Node,同时A是SpanIdA,B是SpanIdB,因为是A调B,那么B的parentSpanId就是SpanIdA了】

Pinpoint通过TransactionId查找关联的多个Span,同时根据SpanId和ParentSpanId对他们进行层次关系排序。

2.2.2.1、TransactionId

由agentId,JVM启动时间以及一个序列号组成

2.2.2.2、agentId
  1. JVM启动时用户创建的id
  2. 在安装了Pinpoint的整个服务器组内必须唯一,最简单的方式是使用主机名hostname,因为通常主机名不会重复,如果你需要在服务器组运行多个JVM,可以在主机名前加下前缀避免重复
2.2.2.3、jvm启动时间
  1. 用于保证生成的序列号是唯一的(SequenceNumber)
  2. 这个值用来当用户不小心创建了相同的agentId时避免transactionId冲突
2.2.2.4、SequenceNumber

Pinpoint agent产生的id,从0开始递增,每条消息都会产生一个

2.2.2.4、TransactionId 冲突
  1. Dapper和Zipkin(Twitter的一个分布式追踪平台),随机生成TraceIds(对应Pinpoint中的TransactionIds),认为id冲突是很常见场景。但是,在Pinpoint中,我们努力避免这种冲突,有以下两个选择
    1. 选择1:id的数据量小但是冲突的可能性很高
    2. 选择2:id的数据量大但是冲突的可能性低
  2. Pinpoint选择的是2【也就是说pinpoint为了使TransactionId的冲突减少,TransactionId的数据会大点】

可能有更好的方法来解决这个问题,我们提出几个想法,比如通过一个中心key服务器来产生key【这里指的应该就是前面的TransactionId】,但是由于性能和网络问题没有实现。目前我们仍然在考虑批量产生key作为替代解决方案。因此在将来,可能会开发出这样的方法,但就目前而言,我们采用了一种简单的方法。在Pinpoint中,TransactionId是可变的数据。

2.3、字节码注入,无需修改代码

前面我们解释了分布式事务追踪。一种方法是让开发者修改自己的代码,当发生RPC调用的时候允许开发者增加标记信息,然后修改代码负担比较重。

Twitter的Zipkin使用修改后的类库以及容器(Finagle)来提供分布式追踪的功能,然后它同样需要开发者修改代码,我们想要的是不修改代码就能实现追踪功能,同时提供代码级别的可见性,Pinpoint使用了字节码注入技术(bytecode instrumentation),Pinpoint的Agent干预(拦截)调用RPC的代码以便自动处理标记信息。

2.3.1、克服字节码注入的缺点

有两种方式实现分布式追踪。字节码注入是一种自动的方式

2.3.1.1、手动方式

开发者使用Pinpoint提供的api在关键位置记录数据

2.3.1.2、自动方式

开发者无需修改代码,Pinpoint决定那些代码需要干预和增强

2.3.2、两种方式的优缺点

在这里插入图片描述

尽管字节码注入需要很多的开发资源,但是部署应用的时候几乎不需要(即开发难,部署运行简单)

2.4、字节码注入的价值

我们使用字节码注入来实现,除了上面提到的,还有下面一些理由

2.4.1、隐藏API

如果API是暴露给开发者使用,作为API的提供者,想根据我们的需要修改API时往往会受到限制,这种限制会给我们很多压力

我们可能会修改API来纠正错误或者添加新功能,然而如果修改受到限制的话,我们很难完善API,解决此类问题的最佳答案是可扩展的系统设计,这并不是每个人都知道的简单选择。 创建完美的API设计几乎是不可能的,因为我们无法预测未来。

通过字节码注入技术,我们不用担心暴露追踪API导致的这些问题,也可以持续完善设计而不用考虑依赖关系。

想要使用Pinpoint开发应用的开发者需要注意API可能会修改,因为我们的第一优先级是提高性能和设计。

2.4.2、容易开始或者关闭

使用字节码注入的缺点是当分析的类库或者Pinpoint本身有问题时,你的应用程序会受到影响,但是你不需要修改代码,只需要禁用Pinpoint即可解决这种问题

通过在JVM启动脚本中添加以下三行(与Pinpoint Agent的配置相关联),可以轻松地为应用程序启用Pinpoint:

-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar
-Dpinpoint.agentId=<Agent's UniqueId>
-Dpinpoint.applicationName=<The name indicating a same service (AgentId collection)>

如果因为Pinpoint导致什么问题,只需要删除启动参数里的配置

2.5、字节码注入的原理

在这里插入图片描述

在Pinpoint中,我们通过抽象出拦截器提高开发效率和可访问性,Pinpoint在类加载时干预应用程序代码,注入必要的代码来跟踪分布式请求以及性能信息,由于跟踪代码直接注入应用程序代码,因此这提高了性能。

在Pinpoint中,API拦截部分和数据记录部分是分开的。 拦截器被注入到我们想要跟踪的方法中,并调用before()和after()方法来处理数据记录。 通过字节码检测,Pinpoint Agent只从必要的方法记录数据,这使得分析数据的大小变得紧凑。

2.6、如何优化Pinpoint agent的性能

2.6.1、使用二进制格式(Thrift)

你可以使用二进制格式(Thrift)加快编码速度,尽管它很难使用和调试,但是可以提高网络利用率以及减小生成的数据大小

2.6.2、使用可变长度编码和格式优化记录数据

如果你把一个长整型转化成定长的字符串,数据大小是8字节,但是,如果你使用可变长度编码,跟你长整型数字的大小,转化后的数据长度可能是1到10字节。为了减少数据大小,Pinpoint通过Compact Protocol of Thrift将数据编码为可变长度字符串,并记录要针对编码格式进行优化的数据。

Pinpoint Agent通过将基于根方法的剩余时间转换为矢量值来减少数据大小。

2.6.2.1、定长编码和可变长度编码的比较

在这里插入图片描述

  1. 有3个不同的方法开始调用和完成调用,你需要在这6处记录时间
  2. 其中A方法包含以下2个方法
    1. B方法
    2. C方法
2.6.2.2、采用定长编码
  1. 需要48字节(6处*8字节)
  2. 1处方法调用需要8字节
2.6.2.3、使用可变长度编码

根据其对应的格式记录数据,同时以根方法的起始时间为基准,计算其他方法处与基准的差异(矢量值)来计算时间,由于向量值是一个小数字,它消耗少量字节,因此只消耗13个字节而不是48字节

A方法 开始调用处:8个字节
B方法 开始调用处:1个字节
B方法 完成调用处:1个字节
C方法 开始调用处:1个字节
C方法 完成调用处:1个字节
A方法 完成调用处:1个字节
2.6.2.4、总结

如果执行方法需要更多时间,即使使用可变长度编码,也会增加字节数。 但是,它仍然比固定长度编码更有效。

2.6.3、使用常量表替换重复的API信息,SQL语句以及字符串

我们希望pinpoint能够实现代码级别的追踪,然而会造成数据变大的问题,每次高精度的数据发送到服务端,数据变大会增加带宽开销。

为了解决这种问题,我们采用了一种策略,通过在远程的HBase服务中创建一张常量表,既然每次发送"方法A"的数据到Pinpoint的Collector都有负载,Pinpoint Agent把"方法A"的数据转化成一个ID并在HBase的常量表中存储方法A的信息,并且使用这个ID继续后面的追踪,当用户在网站上检索跟踪数据时,Pinpoint Web会在常量表中搜索相应ID的方法信息并重新组织它们。

使用同样的方法用于减少SQL或常用字符串中的数据大小

2.6.4、处理批量请求的采样

Naver提供的在线门户服务请求非常庞大。 单个服务每天处理超过200亿个请求。 跟踪此类请求的一种简单方法是根据需要扩展网络基础架构和服务器以满足请求的数量。 但是,这不是处理这种情况的经济有效的方法。

在Pinpoint中,您可以只收集采样数据,而不是跟踪每个请求。 在请求很少的开发环境中,每个数据都会被收集。 在请求很大的生产环境中,只收集整个数据的1~5%,这足以分析整个应用程序的状态。 通过采样,您可以最大限度地减少应用程序中的网络开销,并降低网络和服务器等基础设施的花费。

Pinpoint中的采样方法

Pinpoint支持一个计数采样器(Counting Sampler),如果设置为10,那么只会收集十分之一的请求。

2.6.5、使用异步数据传输最小化被终止的应用线程

Pinpoint不会干挠应用程序线程,因为数据编码和远程消息传输是通过异步线程实现的

2.6.6、通过UDP传输数据

与Google的Dapper不同,Pinpoint通过网络传输数据以确保数据速度。 当数据流量突然爆发时,与你的服务共享网络可能是一个问题。

在这种情况下,Pinpoint Agent开始使用UDP协议,这样你的服务便有网络连接优先级

注意

数据传输的API可以被替换,因为他是独立的接口,你可以替换成不同的实现,比如通过本地文件

2.7、Pinpoint应用样例

2.7.1、场景

  1. 有2个应用,且都集成了Pinpoint
    1. TomcatA
    2. TomcatB
  2. TomcatA 有个test()方法,test()方法 会通过htto 访问 TomcatB的hello方法

在这里插入图片描述

2.7.2、Pinpoint在每个方法中做的事情

  1. 当请求到达TomcatA时,Pinpoint Agent产生了一个TraceId

    1. TX_ID: TomcatA^ TIME^1
    2. SpanId: 10
    3. ParentSpanId: -1(根)
  2. 从Spring MVC controllers中记录数据

  3. 干预 HttpClient.execute()方法的调用,并在HttpGet中组装TraceId

    1. 创建子 TraceId
    2. TX_ID: TomcatA^ TIME^ 1 -> TomcatA^ TIME^1
    3. SPAN_ID: 10->20
    4. PARENT_SPAN_ID: -1->10(父SpanId) --在HTTP头部中配置子TraceId
    5. HttpGet.setHeader(PINPOINT_TX_ID,“TomcatA^ TIME^1”)
    6. HttpGet.setHeader(PINPOINT_SPAN_ID,“20”)
    7. HttpGet.setHeader(PINPOINT_PARENT_SPAN_ID,“10”)
  4. 将打了标记的请求传输到TomcatB

    1. TomcatB检查传输过来的请求头部
    2. HttpServletRequest.getHeader(PINPOINT_TX_ID)–TomcatB成了子节点,因为它识别到了header中的TraceId
    3. TX_ID: TOMCATA^ TIME^1
    4. SPAN_ID: 20
    5. PARENT_SPAN_ID: 10
      1. 注意:到子节点的SPAN_ID其实是由父节点创建好的
  5. 从Spring MVC controllers中记录数据并完成请求

在这里插入图片描述

1. 当TomcatB中的请求完成时
	1. Pinpoint agent将追踪数据发送到Pinpoint Collector
	2. Pinpoint Collector把数据存储到HBase
2. 来自TomcatB的HTTP调用终止后,TomcatA的请求完成
	1. Pinpoint agent将追踪数据发送到Pinpoint Collector
	2. Pinpoint Collector把数据存储到HBase
3. UI从HBase读取跟踪数据,并通过对树进行排序来创建调用堆栈

3、总结

  1. Pinpoint是另一个与你的应用程序一起运行的应用程序。
  2. 使用字节码检测使Pinpoint看起来像是不需要修改代码。
  3. 通常,字节码检测技术使应用程序容易受到风险的影响; 如果Pinpoint出现问题,它也会影响你的应用程序。 但就目前而言,我们并没有摆脱这些威胁,而是专注于提高Pinpoint的性能和设计。 因为我们认为这使得Pinpoint更有价值。 因此,是否使用Pinpoint是由你决定的。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Pinpoint--基础--04--请求追踪和字节码插装 的相关文章

随机推荐

  • Android应用层View绘制流程与源码分析

    转自https blog csdn net yanbober article details 46128379 1 背景 还记得前面 Android应用setContentView与LayoutInflater加载解析机制源码分析 这篇文章
  • MyBatis日常记录之sql片段的抽取

  • python在VScode中中文输出乱码的解决方案

    一 确定python编码 如果不是就点击这个位置会弹出 选择 通过编码重新打开 UTF 8 然后尝试运行 如果问题没有解决进行第二步 二 修改json文件 文件 首选项 设置 搜索 code runner executorMap 点击 在s
  • 16 【跨域】

    16 跨域 1 什么是跨域 跨域 是指浏览器不能执行其他网站的脚本 它是由浏览器的同源策略造成的 是浏览器对JavaScript实施的安全限制 浏览器从一个域名的网页去请求另一个域名的资源时 出现域名 端口 协议任一不同 都属于跨域 同源策
  • 芯片测试(3)——DC测试

    芯片测试 3 DC测试 一 电源电压测试 二 地引脚测试 三 I V测试 四 电阻测试 五 对应测试项 六 简述及测试方法描述 6 1 VIL VIH 加流测压 FIMV 6 2 VOL VOH 加流测压 FIMV 6 3 IIL IIH
  • pip : 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

    换了新电脑 在新电脑中安装了pycharm 但是在使用pip install下载相关库时 出现pip 无法将 pip 项识别为 cmdlet 函数 脚本文件或可运行程序的名称 请检查名称的拼写 如果包括路径 请确保路径正确 然后再试一次 的
  • 给锂电池充电,充电器的输出电压

    目录 老旧充电器的一点小问题 指示灯不亮 拆 丢弃 给锂电池充电 指示灯无法显示充电状态 如何判断电池是否充满电了 电池反向充电 总结 老旧充电器的一点小问题 指示灯不亮 今天遇到一个有意思的问题 我买了两个12V的锂电池 DC接口 于是我
  • 2023年高校大数据实验室建设方案

    大数据实验室建设方案具体内容包括 人才培养方案建设 课程资源建设 师资建设 实验室建设 教学服务建设 泰迪打造国内领先的大数据人工智能及课程资源 包括 商务数据分析实训管理平台 云计算资源管理平台 大数据编程实训平台 商务数据分析编程实训平
  • Unity ScrollView拖不动

    今天再用Unity的imgui的ScrollView的时候 发现UI拖不动 找了好半天 终于招到了原因 在此记录下 代码如下 public Vector2 scrollPosition Vector2 zero void OnGUI scr
  • 移动端与服务端交互安全方案

    系统流程图 验签 解决问题 1 身份验证 是否是我规定的那个人 2 防篡改 是否被第三方劫持并篡改参数 3 防重放 是否重复请求 具体算法 1 约定appKey 保证该调用请求是平台授权过的调用方发出的 保证请求方唯一性 2 将appKey
  • 常用巡检命令

    思科设备 show version 查看系统软 硬件版本信息 show running config 查看设备运行的配置信息 show ip interfaces brief 查看所有接口摘要信息 show interfaces 查看全部接
  • Java中弹出对话框中的几种方式

    1 显示一个错误对话框 该对话框显示的 message 为 alert JOptionPane showMessageDialog null alert alert JOptionPane ERROR MESSAGE 2 显示一个内部信息对
  • JavaSE知识体系目录

    文章目录 Java基础语法知识 关键字 运算符 数据类型 流程控制语句 面向对象 异常和常用类 集合 Collection Map IO 字节流 字符流 线程 网络 Java基础语法知识 关键字 运算符 算数运算符 比较运算符 赋值运算符
  • CSS盒模型自适应布局——calc与box-sizing

    CSS盒模型 1 CSS中盒模型分为两种 第一种是W3C的标准模型 即盒子的宽高等于内容的宽高 盒子的padding和border不计算在内 第二种是IE的传统模型 IE6以下 不含IE6 称为怪异模式或者QuirksMode 即盒子的宽高
  • sklearn中的LASSO

    LASSO import numpy as np import matplotlib pyplot as plt np random seed 42 x np random uniform 3 0 3 0 size 100 X x resh
  • pytorch 笔记: Swin-Transformer 代码

    理论部分 论文笔记 Swin Transformer Hierarchical Vision Transformer using Shifted Windows UQI LIUWJ的博客 CSDN博客 源码部分 Swin Transform
  • Java占位符总结

    文章目录 实现方式 方式一 jdk1 8 java text MessageFormat 方式二 Log4j javaorg slf4j helpers MessageFormatter 方式三 commons text org apach
  • linux下搭建goprotobuf

    linux下搭建goprotobuf 1 搭建go语言环境 参考官网 http golang org doc install 主要是设置好GO PATH这个变量 这个就是你的工作环境目录 可以使用go env来查询设置好了没 2 搭建pro
  • python中列表概念,Python基本数据类型——List(列表)

    1 序列 1 1 序列的基本概念 序列是Python中最基本的一种数据结构 序列用于保存一组有序的数据 所有的数据在序列当中都有一个唯一的位置 索引 并且序列中的数据会按照添加的顺序来分配索引 数据结构是指计算机中数据存储的方式 1 2 序
  • Pinpoint--基础--04--请求追踪和字节码插装

    Pinpoint 基础 04 请求追踪和字节码插装 备注 背景 英文原文 https naver github io pinpoint 1 8 4 techdetail html Dapper原文 https ai google resea