【SkyWalking】SkyWalking是如何实现跨进程传播链路数据?

文章目录

  • 一、简介
    • 1 为什么写这篇文章
    • 2 跨进程传播协议-简介
  • 二、协议
    • 1 Standard Header项
    • 2 Extension Header项
    • 3 Correlation Header项
  • 三、跨进程传播协议的源码分析
    • 1 OpenTracing规范
    • 2 通过dubbo插件分析跨进程数据传播
    • 3 分析跨进程传播协议的核心源码
  • 四、小结
  • 参考

一、简介

1 为什么写这篇文章

写这篇文章是为了让自己和大家梳理这些内容:

  1. SkyWalking的链路串联依赖跨进程数据传播,他的跨进程传播协议是怎样的?
  2. 如果我想借助SkyWalking的跨进程传播协议实现传递全链路业务数据(如全局userId等),该如何实现?

2 跨进程传播协议-简介

SkyWalking 跨进程传播协议是用于上下文的传播,之前经历过sw3协议、sw6协议,本文介绍是当前(2023年)最新的sw8协议。
该协议适用于不同语言、系统的探针之间传递上下文。

二、协议

Header项分为三类:

  • Standard Header项,Header名称:sw8
  • Extension Header项,Header名称:sw8-x
  • Correlation Header项,Header名称:sw8-correlation

协议的整体设计:
在这里插入图片描述

下面详细讲解协议的Header项:

1 Standard Header项

该Header项是上下文传播必须包含的。

  • Header名称:sw8.
  • Header值:由-分隔的8个字段组成。Header值的长度应该小于2KB。

Header值中具体包含以下8个字段:

  • 采样(Sample),0 或 1,0 表示上下文存在,但是可以(也很可能)被忽略而不做采样;1 表示这个trace需要采样并发送到后端。
  • 追踪ID(Trace Id),是 Base64 编码的字符串,其内容是由 . 分割的三个 long 类型值, 表示此trace的唯一标识。
  • 父追踪片段ID(Parent trace segment Id),是 Base64 编码的字符串,其内容是字符串且全局唯一。
  • 父跨度ID(Parent span Id),是一个从 0 开始的整数,这个跨度ID指向父追踪片段(segment)中的父跨度(span)。
  • 父服务名称(Parent service),是 Base64 编码的字符串,其内容是一个长度小于或等于50个UTF-8编码的字符串。
  • 父服务实例标识(Parent service instance),是 Base64 编码的字符串,其内容是一个长度小于或等于50个UTF-8编码的字符串。
  • 父服务的端点(Parent endpoint),是 Base64 编码的字符串,其内容是父追踪片段(segment)中第一个入口跨度(span)的操作名,由长度小于或等于50个UTF-8编码的字符组成。
  • 本请求的目标地址(Peer),是 Base64 编码的字符串,其内容是客户端用于访问目标服务的网络地址(不一定是 IP + 端口)。

示例值: 1-TRACEID-SEGMENTID-3-PARENT_SERVICE-PARENT_INSTANCE-PARENT_ENDPOINT-IPPORT

2 Extension Header项

该Header项是可选的。扩展Header项是为高级特性设计的,它提供了部署在上游和下游服务中的探针之间的交互功能。

Header名称:sw8-x

Header值:由-分割,字段可扩展。

扩展Header值
当前值包括的字段:

追踪模式(Tracing Mode),空、0或1,默认为空或0。表示在这个上下文中生成的所有跨度(span)应该跳过分析。在默认情况下,这个应该在上下文中传播到服务端,除非它在跟踪过程中被更改。
客户端发送的时间戳:用于异步RPC,如MQ。一旦设置,消费端将计算发送和接收之间的延迟,并使用key transmission.latency自动在span中标记延迟。

示例值:1-1621344125000

3 Correlation Header项

该Header项是是可选的。并非所有语言的探针都支持,已知的是Java的探针是支持该协议。
该Header项用于跨进程传递用户自定义数据,例如userId、orgId。
这个协议跟OpenTracing 的 Baggage很类似,但是Correlation Header项相比,在默认设置下会更有更严格的限制,例如,只能存放3个字段,且有字段长度限制,这个是为了安全、性能等考虑。
数据格式:

Header名称:sw8-correlation

Header值:由,分割一对对key、value,每对key、value逗号分割,key、value的由Base64编码。

示例值:a2V5MQ==:dmFsdWUx,a2V5LTI=:dmFsdWUy

三、跨进程传播协议的源码分析

1 OpenTracing规范

SkyWalking是基于OpenTracing标准的追踪系统,参考吴晟老师翻译的OpenTracing规范的文章opentracing之Inject和Extract,OpenTracing定义了跨进程传播的几个要素:

SpanContext:SpanContext代表跨越进程边界,传递到下级span的状态。在SkyWalking中的实现类是org.apache.skywalking.apm.agent.core.context.TracingContext
Carrier:传递跨进程数据的搬运工,负责将追踪状态从一个进程"carries"(携带,传递)到另一个进程
Inject 和 Extract:SpanContexts可以通过Inject(注入)操作向Carrier增加,或者通过Extract(提取)从Carrier中获取,跨进程通讯数据(例如:HTTP头)。通过这种方式,SpanContexts可以跨越进程边界,并提供足够的信息来建立跨进程的span间关系(因此可以实现跨进程连续追踪)

2 通过dubbo插件分析跨进程数据传播

我们以SkyWalking java agent的dubbo-2.7.x-plugin插件为例,其中跨进程传播数据的核心代码在org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor,下面是该类跨进程传播的核心代码:

public class DubboInterceptor implements InstanceMethodsAroundInterceptor {/*** Consumer: The serialized trace context data will* inject to the {@link RpcContext#attachments} for transport to provider side.* <p>* Provider: The serialized trace context data will extract from* {@link RpcContext#attachments}. current trace segment will ref if the serialization context data is not null.*/@Overridepublic void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,MethodInterceptResult result) throws Throwable {......if (isConsumer) { // 1、consumer端// ContextCarrierfinal ContextCarrier contextCarrier = new ContextCarrier();// 1.1 createExitSpan()内部会调用TracerContext.inject(carrier),将TracerContext中的context数据inject(注入)到ContextCarrier的context中span = ContextManager.createExitSpan(generateOperationName(requestURL, invocation), contextCarrier, host + ":" + port);CarrierItem next = contextCarrier.items();// 1.2 遍历ContextCarrier,从ContextCarrier的context获取数据,注入到dubbo的attachment,从consumer端传递到provider端while (next.hasNext()) {next = next.next();rpcContext.setAttachment(next.getHeadKey(), next.getHeadValue());if (invocation.getAttachments().containsKey(next.getHeadKey())) {invocation.getAttachments().remove(next.getHeadKey());}}} else { // 2 provider端// 2.1 从consumer端传递到provider端的attachment中获取跨进程协议数据,然后设置到contextContextCarrier contextCarrier = new ContextCarrier();CarrierItem next = contextCarrier.items();while (next.hasNext()) {next = next.next();next.setHeadValue(rpcContext.getAttachment(next.getHeadKey()));}// 2.2 createEntrySpan()内部会调用TracerContext.extract(carrier),将ContextCarrier的context数据extract(提取)到将TracerContext中的context中span = ContextManager.createEntrySpan(generateOperationName(requestURL, invocation), contextCarrier);span.setPeer(rpcContext.getRemoteAddressString());}}
}

从上面的源码可以看出在服务调用方和被调用方,都会用到ContextCarrier,他是临时搬运工,负责两个进程的TracerContext数据的传递。
下面分析ContextCarrier等类的核心源码。

3 分析跨进程传播协议的核心源码

TracingContext
org.apache.skywalking.apm.agent.core.context.TracingContext是OpenTracing的SpanContext的一种实现,里面包含了span的上下文,包含在segment、correlationContext、extensionContext,而inject()、extract()负责跨进程上下文透传。

public class TracingContext implements AbstractTracerContext {/*** The final {@link TraceSegment}, which includes all finished spans.*/private TraceSegment segment;@Getter(AccessLevel.PACKAGE)private final CorrelationContext correlationContext;@Getter(AccessLevel.PACKAGE)private final ExtensionContext extensionContext;/*** Prepare for the cross-process propagation. How to initialize the carrier, depends on the implementation.** @param carrier to carry the context for crossing process.*/void inject(ContextCarrier carrier);/*** Build the reference between this segment and a cross-process segment. How to build, depends on the* implementation.** @param carrier carried the context from a cross-process segment.*/void extract(ContextCarrier carrier);
}

ContextCarrier
ContextCarrier作为传递跨进程数据的搬运工,负责将追踪状态从一个进程"carries"(携带,传递)到另一个进程,其中包含了sw8协议里的Standard Header项、Extension Header项、Correlation Header项相关的上下文数据,具体参考下面的代码:

public class ContextCarrier implements Serializable {/*** extensionContext包含了在某些特定场景中用于增强分析的可选上下文,对应sw8的Extension Header项*/private ExtensionContext extensionContext = new ExtensionContext();/*** 用户的自定义上下文容器。此上下文与主追踪上下文一同传播。对应sw8的Correlation Header项*/private CorrelationContext correlationContext = new CorrelationContext();/*** @return 存在于当前tracing上下文中的item清单*/public CarrierItem items() {SW8ExtensionCarrierItem sw8ExtensionCarrierItem = new SW8ExtensionCarrierItem(extensionContext, null);SW8CorrelationCarrierItem sw8CorrelationCarrierItem = new SW8CorrelationCarrierItem(correlationContext, sw8ExtensionCarrierItem);SW8CarrierItem sw8CarrierItem = new SW8CarrierItem(this, sw8CorrelationCarrierItem);return new CarrierItemHead(sw8CarrierItem);}/*** Extract the extension context to tracing context*/void extractExtensionTo(TracingContext tracingContext) {tracingContext.getExtensionContext().extract(this);// The extension context could have field not to propagate further, so, must use the this.* to process.this.extensionContext.handle(tracingContext.activeSpan());}/*** Extract the correlation context to tracing context*/void extractCorrelationTo(TracingContext tracingContext) {tracingContext.getCorrelationContext().extract(this);// The correlation context could have field not to propagate further, so, must use the this.* to process.this.correlationContext.handle(tracingContext.activeSpan());}/*** 序列化sw8的Standard Header项,使用 '-' 分割各个字段* Serialize this {@link ContextCarrier} to a {@link String}, with '|' split.* @return the serialization string.*/String serialize(HeaderVersion version) {if (this.isValid(version)) {return StringUtil.join('-',"1",Base64.encode(this.getTraceId()),Base64.encode(this.getTraceSegmentId()),this.getSpanId() + "",Base64.encode(this.getParentService()),Base64.encode(this.getParentServiceInstance()),Base64.encode(this.getParentEndpoint()),Base64.encode(this.getAddressUsedAtClient()));}return "";}/*** 反序列化sw8的Standard Header项* Initialize fields with the given text.* @param text carries {@link #traceSegmentId} and {@link #spanId}, with '|' split.*/ContextCarrier deserialize(String text, HeaderVersion version) {if (text == null) {return this;}if (HeaderVersion.v3.equals(version)) {String[] parts = text.split("-", 8);if (parts.length == 8) {try {// parts[0] is sample flag, always trace if header exists.this.traceId = Base64.decode2UTFString(parts[1]);this.traceSegmentId = Base64.decode2UTFString(parts[2]);this.spanId = Integer.parseInt(parts[3]);this.parentService = Base64.decode2UTFString(parts[4]);this.parentServiceInstance = Base64.decode2UTFString(parts[5]);this.parentEndpoint = Base64.decode2UTFString(parts[6]);this.addressUsedAtClient = Base64.decode2UTFString(parts[7]);} catch (IllegalArgumentException ignored) {}}}return this;}
}

CorrelationContext
ContextCarrier里包含里sw8的Correlation Header项存放于CorrelationContext,这个类非常有用,适合我们去在全链路跨进程传递自定义的数据。
sw8协议里的Standard Header项、Extension Header项是比较固定的协议格式,我们可以扩展这些协议,例如Standard Header项,当前固定是8位的,对应8个字段,我们可以扩展为9位,第九位可以定义为userId。但是如果要这样改造,就得修改ContextCarrier类序列化、反序列的逻辑,要重新发布agent,并考虑好新旧版本兼容性问题、以及不同语言的agent是否兼容。
而sw8的Correlation Header项使用起来就非常方便。先看下对应实现了CorrelationContext的源码:

/*** Correlation context, use to propagation user custom data.* Correlation上下文,用于传播用户自定义数据*/
public class CorrelationContext {private final Map<String, String> data;/*** Add or override the context. 添加或覆盖上下文数据** @param key   to add or locate the existing context* @param value as new value* @return old one if exist.*/public Optional<String> put(String key, String value) {// 可以存放于span的tag中if (AUTO_TAG_KEYS.contains(key) && ContextManager.isActive()) {ContextManager.activeSpan().tag(new StringTag(key), value);}// settingdata.put(key, value);return Optional.empty();}/*** @param key to find the context 获取上下文数据* @return value if exist.*/public Optional<String> get(String key) {return Optional.ofNullable(data.get(key));}/*** Serialize this {@link CorrelationContext} to a {@link String} 序列化** @return the serialization string.*/String serialize() {if (data.isEmpty()) {return "";}return data.entrySet().stream().map(entry -> Base64.encode(entry.getKey()) + ":" + Base64.encode(entry.getValue())).collect(Collectors.joining(","));}/*** Deserialize data from {@link String} 反序列化*/void deserialize(String value) {if (StringUtil.isEmpty(value)) {return;}for (String perData : value.split(",")) {// Only data with limited count of elements can be addedif (data.size() >= Config.Correlation.ELEMENT_MAX_NUMBER) {break;}final String[] parts = perData.split(":");if (parts.length != 2) {continue;}data.put(Base64.decode2UTFString(parts[0]), Base64.decode2UTFString(parts[1]));}}/*** Prepare for the cross-process propagation. Inject the {@link #data} into {@link* ContextCarrier#getCorrelationContext()}*/void inject(ContextCarrier carrier) {carrier.getCorrelationContext().data.putAll(this.data);}/*** Extra the {@link ContextCarrier#getCorrelationContext()} into this context.*/void extract(ContextCarrier carrier) {......}/*** Clone the context data, work for capture to cross-thread. 克隆数据,用于跨线程传递*/@Overridepublic CorrelationContext clone() {final CorrelationContext context = new CorrelationContext();context.data.putAll(this.data);return context;}/*** Continue the correlation context in another thread.传递到另外的线程** @param snapshot holds the context.*/void continued(ContextSnapshot snapshot) {this.data.putAll(snapshot.getCorrelationContext().data);}
}

通过源码可知,CorrelationContext通过Map<String, String>来存放数据,CorrelationContext数据支持跨线程、跨进程透传。

四、小结

分析Dubbo插件的跨进程核心代码,了解了跨进程传播协议的核心实现逻辑。

其实在其他分布式追踪系统(如Zipkin、Jager)、全链路灰度系统等涉及到跨进程数据传播的系统中,也是使用了类似于上面SkyWalking协议的思路。

参考

SkyWalking Cross Process Propagation Headers Protocol
SkyWalking Cross Process Correlation Headers Protocol
详解 Apache SkyWalking 的跨进程传播协议

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/97741.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++变量默认初始化

初始化不是赋值&#xff0c;初始化是指创建变量时赋予一个初始值&#xff0c;赋值是指将变量的当前值擦除&#xff0c;赋予新值。 如果定义变量时没有初始化&#xff0c;则变量会被系统默认初始化。“默认值”取决于变量的&#xff1a;类型位置 startmindmap * C变量默认初始…

对于无法直接获取URL的数据爬虫

在爬学校安全教育题库的时候发现题库分页实际上执行了一段js代码&#xff0c;如下图所示 点击下一页时是执行了函数doPostBack&#xff0c;查看页面源码如下 点击下一页后这段js提交了一个表单&#xff0c;随后后端返回对应数据&#xff0c;一开始尝试分析获取对应两个参数&a…

【虚拟机】桥接模式下访问外网

目录 一、桥接模式的作用原理 二、配置桥接模式实现外网访问 1、设置 VMnet0 要桥接的网卡 2、虚拟机选择 VMnet0 网卡 3、手动配置虚拟机IP 一、桥接模式的作用原理 桥接模式相当于在当前局域网里创立了一个单独的主机&#xff0c;该主机桥接到宿主主机的网卡&#xff0…

细粒度特征提取和定位用于目标检测:PPCNN

1、简介 近年来&#xff0c;深度卷积神经网络在计算机视觉上取得了优异的性能。深度卷积神经网络以精确地分类目标信息而闻名&#xff0c;并采用了简单的卷积体系结构来降低图层的复杂性。基于深度卷积神经网络概念设计的VGG网络。VGGNet在对大规模图像进行分类方面取得了巨大…

uCOSIII实时操作系统 三 移植

目录 uCOSIII简介&#xff1a; 准备工作&#xff1a; 准备基础工程&#xff1a; UCOSIII工程源码&#xff1a; UCOSIII移植&#xff1a; 向基础工程中添加相应的文件夹 向工程中添加分组 常见问题&#xff1a; 下载验证&#xff1a; uCOSIII简介&#xff1a; UCOS-I…

【Nginx学习】—Nginx基本知识

【Nginx学习】—Nginx基本知识 一、什么是Nginx Nginx是一个高性能的HTTP和反向代理的web服务器&#xff0c;Nginx是一款轻量级的Web服务器/反向代理服务器处理高并发能力是十分强大的&#xff0c;并且支持热部署&#xff0c;启动简单&#xff0c;可以做到7*24不间断运行。 …

SketchyCOCO数据集进行前景图像、背景图像和全景图像的分类

SketchyCOCO数据集进行前景图像、背景图像和全景图像的分类 import os import shutildef CopyFile(src, dst, filename):if not os.path.exists(dst):os.makedirs(dst)print(create dir: dst)try:shutil.copy(src\\filename, dst\\filename)except Exception as e:print(cop…

计算机网络-计算机网络体系结构-物理层

目录 一、通信基础 通信方式 传输方式 码元 传输率 *二 准则 2.1奈氏准则(奈奎斯特定理) 2.2香农定理 三、信号的编码和调制 *数字数据->数字信号 数字数据->模拟信号 模拟数据->数字信号 模拟数据->模拟信号 *四、数据交换方式 电路交换 报文交换…

kafka客户端应用参数详解

一、基本客户端收发消息 Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可&#xff1a; <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka_2.13</artifactId><version>3.4.0</version></depend…

力扣 -- 516. 最长回文子序列

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int longestPalindromeSubseq(string s) {int ns.size();vector<vector<int>> dp(n,vector<int>(n));//记得从下往上填表for(int in-1;i>0;i--){//记得i是小于等于j的for(int ji;j&l…

山体滑坡监测系统——高效、便捷的新选择

在当今社会&#xff0c;科技的进步为我们的生活和工作带来了诸多便利。而在山体滑坡监测领域&#xff0c;全球导航卫星系统&#xff08;GNSS&#xff09;的引入更是增加了数据监测的高效性和便捷性。 一、山体滑坡监测系统的基本原理 山体滑坡监测系统是由监控平台和GNSS位移…

2.6 宽带接入技术

思维导图&#xff1a; 前言&#xff1a; 我的理解&#xff1a; 1. **早期互联网接入技术的局限性**&#xff1a; - 作者首先回顾了早期用户通过电话线和调制解调器连接到互联网服务提供商&#xff08;ISP&#xff09;的方式&#xff0c;指出这种方式的速度上限是56 kbit/…

UE5.1编辑器拓展【三、脚本化资产行为,删除无引用资产】

目录 需要考虑的问题 重定向的修复函数 代码&#xff1a; 删除无引用资产 代码 需要添加的头文件和模块 在我们删除资产的时候&#xff0c;会发现&#xff0c;有些资产在删除的时候会出现有被什么什么引用&#xff0c;还有的是没有被引用。 而我们如果直接选择一片去进行…

PHP 伪协议:使用 php://input 访问原始 POST 数据

文章目录 参考环境PHP 伪协议概念为什么需要 PHP 伪协议&#xff1f; php://input为什么需要 php://input&#xff1f;更灵活的数据处理减小性能压力 发送 POST 数据HackBarHackBar 插件的获取 $_POST打开 HackBar 插件通过 HackBar 插件发起 POST 请求 基操 enable_post_data_…

ROS机械臂开发-开发环境搭建【一】

目录 前言环境配置docker搭建Ubuntu环境安装ROS 基础ROS文件系统 bugs 前言 想系统学习ROS&#xff0c;做一些机器人开发。因为有些基础了&#xff0c;这里随便写写记录一下。 环境配置 docker搭建Ubuntu环境 Dockerfile # 基础镜像 FROM ubuntu:18.04 # 设置变量 ENV ETC…

[开源]基于Vue的拖拽式数据报表设计器,为简化开发提高效率而生

一、开源项目简介 Cola-Designer 是一个 基于VUE&#xff0c;实现拖拽 配置方式生成数据大屏&#xff0c;为简化开发、提高效率而生。 二、开源协议 使用GPL-2.0开源协议 三、界面展示 概览 部分截图&#xff1a; 四、功能概述 特性 0 代码 实现完全拖拽 配置式生成…

【好玩】如何在github主页放一条贪吃蛇

前言 &#x1f34a;缘由 github放小蛇&#xff0c;就问你烧不烧 起因看到大佬github上有一条贪吃蛇扭来扭去&#xff0c;觉得好玩&#xff0c;遂给大家分享一下本狗的玩蛇历程 &#x1f95d;成果初展 贪吃蛇 &#x1f3af;主要目标 实现3大重点 1. github设置主页 2. git…

C#学习系列相关之多线程(二)----Thread类介绍

一、线程初始化 1.无参数 static void Main(string[] args) {//第一种写法Thread thread new Thread(test);thread.Start();//第二种写法 delegateThread thread1 new Thread(new ThreadStart(test));thread1.Start();//第三种写法 lambdaThread thread2 new Thread(() >…

Java版工程行业管理系统源码-专业的工程管理软件-提供一站式服务

项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部工程管理的提升提出了更高的要求。 二、企业通过数字化转型&#xff0c;不仅有利于优化业务流程、提升经营管理…

LeakyReLU激活函数

nn.LeakyReLU 是PyTorch中的Leaky Rectified Linear Unit&#xff08;ReLU&#xff09;激活函数的实现。Leaky ReLU是一种修正线性单元&#xff0c;它在非负数部分保持线性&#xff0c;而在负数部分引入一个小的斜率&#xff08;通常是一个小的正数&#xff09;&#xff0c;以防…