【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

深入可观测底层:OpenTelemetry 链路传递核心原理

  • 2022-06-22
  • 本文字数:5983 字

    阅读完需:约 20 分钟

深入可观测底层:OpenTelemetry 链路传递核心原理

前言


本文会系统讲解链路传递一些基本概念,同时结合案例讲解链路传递的过程。

Context、Propagator 的概念


我们先看一张在 HTTP 通信下,进行链路传播的例子。



图中是一个 Client 请求 Server 通信,同时 Client 把自己的链路信息传递给 Server 的过程

可以看到,链路信息通过一个叫 TraceContext 对象封装起来,通过 Http Headers

来存取这个对象,最后达到传播的效果,这里面 TraceContext 就是一个 Context 上下文对象。

 

Context 是一种传播机制,它跨越 API 边界,在逻辑执行单元间传递执行范围的值。链路追踪系统在系统进程内部,进程间通过共享进程的 Context 上下文 对象传递链路信息。


刚才 Http Headers 起到了一个传递 Context 载体作用,在链路追踪系统,我们把这样的载体统称为 Propagators。上面传递过程我们用另一张图看看 Propagators 和 Context 的关系:

 


什么是 Propagator?OpenTelemetry 是这样定义的


Cross-cutting concerns send their state to the next process using Propagators,  which are defined as objects used to read and write context data to and from messages exchanged by the applications.


大概意思,横切关注点通过它,在进程间进行状态的传递。这里,又牵涉了一个重要的知识点。

Cross-cutting concerns 的概念


关注点:如果系统按功能划分,关注点就是系统的核心功能。 横切关注点:从整个系统横向维度看到的核心功能。比如日志功能就是横切关注点的一个典型案例。日志功能往往横跨系统中的每个业务模块。图可能比文字来得更形象:



横切关注点(系统基本功能)通过 Propagator 可以传递它的状态给下一个进程,Propagator 是通过应用程序读写上下文数据进行数据的交换。 每个关注点都包含了一组 Propagator Propagator 把每一个横切关注点通过上下文去 Injection 注入和 Extract 提取。

 

链路传递一般通过特定的请求拦截器库来实现,拦截器通过拦截输入和输出请求,利用对 Propagator 的注入和提取操作来实现传递。


Propagator API 用户可以通过检测库来实现 Propagator 操作的 API。

Baggage 链路传递初探


在一个链路追踪过程中,我们一般会有多个 Span 操作, 为了把调用链状态在 Span 中传递下去,期望最终保存下来,比如打入日志、保存到数据库。SpanContext 会封装一个键值对集合,我们叫它Baggage .

Baggage 会在一条追踪链路上的所有 Span 内全局传输。原理是上一个 Span 将 Context 中 Baggage 注入到 Propagator ,Propagator 和目的 Span 联系,从 Propagator 提取出Baggage ,再写入到目的 Span 的 Context 中。


OpenTelemetry 中关于 Baggage 原文定义:

Baggage is used to annotate telemetry, adding context and information to metrics, traces, and logs. It is a set of name/value pairs describing user-defined properties.


通过 Baggage 我们可以实现强大的追踪功能。例如:在用户的手机端添加一个 Baggage 元素,并通过层层服务调用,传递到最后数据库的操作。有了 Baggage,如果我想定位请求中消耗很大的 SQL 语句是来自于具体哪个手机端用户从 Trace 中一看便知。反过来,我们可以知道一个手机端用户它依次做了哪些 SQL 语句查询。这比你登录服务器一台台找前端、后端日志,还要把他们关联起来分析,要容易很多。


方便理解,我们演示 Baggage 效果:


首先,我们在 loadBalancer 请求中加一个 Baggage,loadBalancer 请求了 resource 服务:

@GetMapping("/loadBalancer")@ResponseBodypublic String loadBalancer(String tag){    Span span = Span.current();          //保存 Baggage	Baggage.current()	  .toBuilder()	  .put("app.username", "蒋志伟")	  .build()	  .makeCurrent();......    ##请求 resourcehttpTemplate.getForEntity(APIUrl+"/resource",String.class).getBody();  
复制代码


然后我们从 resource 服务中获取 Baggage 信息,并把它存储到 Span 的 Attributes 中:

@GetMapping("/resource")@ResponseBodypublic String resource(){	String baggage = Baggage.current().getEntryValue("app.username");	Span spanCur = Span.current();     ##获取当前的 Span,把 Baggage 写的 resource		spanCur.setAttribute("app.username",                          "baggage 传递过来的 value: "+baggage);
复制代码


最终,我们从跟踪系统的链路 UI 中点击 resource 这个 Span,找到传递的 Baggage 信息:

图五:展示 Baggage 的传递

 

当然,Baggage 拥有强大功能,也会有很大的消耗。由于 Baggage 的全局传输,每个键值都会被拷贝到每一个本地(local)及远程的子 Span,如果包含的数量量太大,或者元素太多,它将降低系统的吞吐量或增加 RPC 的延迟。

Tag 链路标签和业务监控


我们进行系统链路追踪,除了 Trace 本身自带信息,如果我们还希望添加自己关注的监控。Trace 支持用打标签 Tags 方式来实现。Tags 本质还是 Span 的 Attributes(在 OpenTelemetry 定义中,统称 Attributes。在 Prometheus、Jaeger 里面沿袭 Tags 老的叫法)。

 

打 Tags 的过程,其实就是在 Span 添加我们自定义的 Attributes 信息,这些 Tags 大部分和我们业务息息相关,为了更方便做业务监控、分析业务。

 

我们看一个 Java 打 Tags 的例子:页面定义好了一个 Tag,名字叫"username"。我们输入 Tags 的值,然后把 Tags 通过一个 HTTP 请求发送给付账单的 API,


图六:打 Tags 的演示


 API 获取 Tags 后,把它保存到当前 Span 的 Attribute 中。这个 Span 对应的是代码里面的一个 gateway 方法, 如果不重名 Span 名称,默认使用 gateway 作为 Span 名称:

@GetMapping("/loadBalancer")public String gateway(String tag){   Span Span = Span.current();          ##获取当前 Span,添加 username 的 tag   Span.setAttribute("username", tag);      ...... }   
复制代码


打了 Tags 后,我们可以在跟踪系统搜索 Tag 关键字。通过 Tag 可以快速找到对应的 Trace:


图七:基于 Tags 的搜索

 

可以看到,根据 Tags 的 key 我们可以很方便筛选想要的 Span 信息。实际场景里,我们面临是从成千上万链路中快速定位访问异常的请求。打 Tags 对我们诊断程序非常有帮助。


我们从 Trace 中找到相应的 Span,能看到 Tag 已经注入到 gateway 的 Span 中:


图八:Tags 的用法


 Baggage 和 Span Tags 的区别


  • Baggage 在全局范围内,(伴随业务系统的调用)在所有 Span 间传输数据。Tags 不会进行传输,因为他们不会被子级的 Span 继承;

  • Span 的 Tags 可以用来记录业务相关的数据,并存储于追踪系统中。

Event 事件 监控更细微的链路行为


一个 Span 下可以添加多个事件,事件是相对于 Span 更小的操作粒度。在我们想知道一个 Span 内各种操作,这种操作期望在监控平台能够分析,Event 就可以满足需求。

 

比如请求一个商品结算的 API,这个 API 调用商品结算方法 Count,Count 里面分别做了查询商品库存,核实商品价格,结算总价这几个步骤,商品库存还是由几次数据库的 SQL 查询聚合的结果。如果这个 API 有异常,查看商品结算这个 Span 往往远远不够,在真实的线上场景,我们还需要追踪每个步骤的请求状态和详细日志。


事件和 Span 的数据结构类似,包含的基本属性:

  • 事件名称;

  • 事件时间:它支持由开发者自定义添加时间,让事件时间更精准;

  • 属性 Attributes:和 Span 一样,用来记录事件相关信息。

	@GetMapping("/event")		public String event() {			Span span = Span.current();			span.updateName("创建eventDemo");				// 手动更新Event持续时间			span.addEvent("time.update", System.currentTimeMillis() + 2000, TimeUnit.MILLISECONDS);			// 给Event添加相关信息			Attributes appInfo = Attributes.of(AttributeKey.stringKey("app.id"), "123456",					AttributeKey.stringKey("app.name"), "应用程序demo");			span.addEvent("appinfo.query", appInfo);			logger.info("this is a event");			return buildTraceUrl(span.getSpanContext().getTraceId());		}
复制代码


我们添加 Event 后,从链路中看到 Event 信息记录在 Trace Logs 中。在事件中,我们还能添加相关 Attributes ,记录事件某些信息:



OpenTelemetry 链路数据如何传播  

Carrier 和 Propagator 的关系

在 OpenTelemetry 中,Trace 的传递中有一个核心的概念,叫 Carrier(搬运工具)。它是 Propagator 用来读取 Context 数据的一种媒介 medium,Carrier 数据格式可能是一个字符 Map 或者一个字符数组。


Carrier 充当"搬运" Span 中 SpanContext 工具。例如 OpenTelemetry 中为了把 Trace 的 Span 信息传递下去,在 HTTP 调用场景中,会有 HttpCarrier,在 RPC 的调用场景中会有 RpcCarrier 来搬运 SpanContext。Trace 通过 Carrier 可以把链路追踪状态在进程中、进程间传递。

数据传播基本操作


回顾刚才一些概念,我们完整描述下 OpenTelemetry Span 在传播中有的基本步骤:


  • StartSpan:Trace 在具体操作中自动生成一个 Span

  • Inject 注入: 将 Span 的 SpanContext 写入到 Carrier 的过程


链路数据为了进行网络传输,需要数据进行序列化和反序列化。这个过程 Trace 通过一个负责数据序列化反序列化上下文的 Formatter 接口实现的。例如在 HttpCarrier 使用中通常就会有一个对应的 HttpFormatter。所以 Inject 注入是委托给 Formatter 将 SpanContext 进行序列化写入 Carrier。


Formatter 提供不同场景序列化的数据格式,叫做 Format 描述。比如:


  • TextMap: 基于字符串的 Map 记录 SpanContext 信息,适用 RPC 网络传输

  • HTTP Headers: 方便解析 HTTP Headers 信息,用于 HTTP 传输


一个 Python 程序实现 Inject 注入过程,Formatter 序列化 SpanContext 成 Text Map 格式:

##Trace 生成一个span    tracer = Tracer()    span = tracer.start_span(operation_name='test')    tracer.inject(        span_context=span.context,        format=Format.TEXT_MAP,        carrier=carrier)
复制代码

 

  • Extract 提取: 将 SpanContext 从 Carrier 中 Extract(提取出来):

span_ctx = tracer.extract(format=Format.TEXT_MAP, carrier={})
复制代码


同理,从 Carrier 提取的过程也需要委托 Formatter 将 SpanContext 反序列化。

运行原理


图一:链路数据在 HTTP 传递

 

我们基于 HTTP 通信解释传播原理。由图一,这个过程大致分为两步:


1、发送端将 SpanContext 注入到请求中,相应伪代码实现:

/**** 将 SpanContext 中的 TraceId,SpanId,Baggage 等根据 format 参数注入到请求中(Carrier)** carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)** err := Tracer.Inject(Span.Context(), opentracing.HTTPHeaders, carrier)**/Inject(sm SpanContext, format interface{}, carrier interface{}) error
复制代码


2、接收端从请求中解析出 SpanContext,相应伪代码实现:

// Inject() takes the `sm` SpanContext instance and injects it for// propagation within `carrier`. The actual type of `carrier` depends on/** 根据 format 参数从请求(Carrier)中解析出 SpanContext(包括 TraceId、SpanId、baggage)。** 例如: **  carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)**  clientContext, err := Tracer.Extract(opentracing.HTTPHeaders, carrier)**/Extract(format interface{}, carrier interface{}) (SpanContext, error)
复制代码


Carrier 负责将追踪状态从一个进程"Carry"(搬运)到另一个进程。对于一个 Carrier,如果已经被 Injected,那么它也可以被 Extracted(提取),从而得到一个 SpanContext 实例。这个 SpanContext 代表着被 Injected 到 Carrier 的信息。

 

说到这里,你可能想知道这个 Carrier 在 HTTP 中具体在哪。其实它就保存到 HTTP 的 Headers 中。而且,W3C 组织为 HTTP 支持链路追踪专门在 Headers 中定义了 Trace 标准:

https://www.w3.org/TR/trace-context/#trace-context-http-headers-format

W3C 组织是对网络标准制定的一个非盈利组织,W3C 是万维网联盟的缩写,像 HTML、XHTML、CSS、XML 的标准就是由 W3C 来定制

跨进程间传播数据


数据传播按照场景分为两类:进程内传播、跨进程间传播 Cross-Process-Tracing。


进程内传播是指 Trace 在一个服务内部传递,监控了服务内部相互调用情况,相当比较简单。追踪系统最困难的部分就是在分布式的应用环境下保持追踪的正常工作。任何一个追踪系统,都需要理解多个跨进程调用间的因果关系,无论他们是通过 RPC 框架、发布-订阅机制、通用消息队列、HTTP 请求调用、UDP 传输或者其他传输模式。所以业界谈起 Tracing 技术 往往说的是跨进程间的分布式链路追踪(Distrubute Tracing)。

 

我们用 OpenTelemetry 实践一个 HTTP 通信的 Trace 例子:


这是一个 Java 程序,我们向下游服务发起一个 HTTP 请求。


程序中使用 OpenTelemetry 的 inject 注入,通过 HTTP Headers 把 Trace 传递给 127.0.0.1:8080 的服务。传播前,手动还创建两个想要一块传播的 Attributes:

@GetMapping("/contextR")@ResponseBodypublic String contextR() {	TextMapSetter<HttpURLConnection> setter = new TextMapSetter<HttpURLConnection>() {		@Override		public void set(HttpURLConnection carrier, String key, String value) {			// 我们把上下文放到 HTTP 的 Header			carrier.setRequestProperty(key, value);		}	};	Span spanCur = Span.current();	Span outGoing = tracer.spanBuilder("/resource").setSpanKind(SpanKind.CLIENT).startSpan();	try {		URL url = new URL("http://127.0.0.1:8080/resource");		HttpURLConnection transportLayer = (HttpURLConnection) url.openConnection();		outGoing.setAttribute("http.method", "GET");		outGoing.setAttribute("http.url", url.toString());					// 将当前 Span 的上下文注入到这个 HTTP 请求中		OpenTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), transportLayer, setter);		// Make outgoing call...
复制代码


运行程序,从监控平台我们看到,Trace 从一个程序成功传递到了 127.0.0.1:8080 的服务:


跨进程传播数据

Github 中文文档


我们 OpenTelemetry Github 社区对链路传播中文介绍,目前还在逐步完善当中。也欢迎大家去贡献更多的中文内容。


  • Propagator 介绍

https://github.com/open-telemetry/docs-cn/blob/main/specification/context/api-propagators.md

  • Context 介绍

https://github.com/open-telemetry/docs-cn/blob/main/specification/context/context.md

  • Baggage 介绍

https://github.com/open-telemetry/docs-cn/blob/main/specification/baggage/api.md

 

作者介绍

蒋志伟,爱好技术的架构师,先后就职于阿里、Qunar、美团,前 pmcaffCTO,目前 OpenTelemetry 中国社区发起人。

欢迎关注我们的 Github 项目:https://github.com/open-telemetry/docs-cn

欢迎大家关注“OpenTelemetry”公众号,这是中国区唯一官方技术公众号。

 

2022-06-22 15:319900

评论

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

测试开发高薪私教线下周末班试听精彩内容回顾

测吧(北京)科技有限公司

测试

超越常规!探索多态在程序设计中的奇妙应用与巧妙技巧!

测吧(北京)科技有限公司

测试

小程序定制开发:解析定制化移动应用的未来

天津汇柏科技有限公司

小程序 小程序开发 小程序开发,软件开发

一次因PageHelper引起的多线程复用问题的排查和解决 | 京东物流技术团队

京东科技开发者

软件测试|人工智能:测试开发的新宠儿

霍格沃兹测试开发学社

素数检验---跨越2000年的人类智慧

fliter

如何实现一个百万亿规模的时序数据库,百度智能云 BTS 架构解析和实践分享

百度Geek说

架构 时序数据库 百度智能云

2024年企业选择堡垒机指南

行云管家

网络安全 数据安全 云安全 堡垒机 企业数据安全

左耳听风 - 关于招聘「读书打卡 day 20」

Java 工程师蔡姬

读书笔记 程序员 个人成长 招聘 职业发展

Walrus 实用教程|Walrus + Gitlab,打通CI/CD 自动化交付!

SEAL安全

开源 gitlab CI/CD Walrus

超越常规!探索多态在程序设计中的奇妙应用与巧妙技巧!

测试人

软件测试

软件测试|探索多态在程序设计中的奇妙应用与巧妙技巧!

霍格沃兹测试开发学社

年度重磅更新!“AI+可视化拖拽”实现个性化页面极速开发!组件设计器即将上线!

优秀

AI 网站开发 组件化开发

Boxy SVG v4.23.2免激活版 mac矢量图编辑器 支持m1/m2

Rose

苹果软件下载 矢量图形编辑软件 Boxy SVG破解版 Boxy SVG mac下载

iTunes Connect 中修改后的内购(IPA)审核所需的时间

雪奈椰子

政务云相关名词解释看这里!

行云管家

云计算 云管平台 云管理 政务云

如何在网页上下载高质量视频和音频文件?推荐使用PullTube for Mac在线视频下载器

Rose

Mac视频下载器 PullTube使用教程 PullTube mac

如何实现一个高扩展、可视化低代码前端框架

互联网工科生

低代码 前端框架 JNPF

软件测试的发展现状与未来趋势

测试人

软件测试

人工智能:测试开发的新宠儿

测吧(北京)科技有限公司

测试

测试开发高薪私教线下周末班试听精彩内容回顾

霍格沃兹测试开发学社

火山引擎ByteHouse:分析型数据库如何设计列式存储

字节跳动数据平台

数据库

ETL怎么实现文件处理

RestCloud

ETL 数据集成 文件处理

通过人力资源规划,实现企业财务计划与分析的转型

智达方通

全面预算 财务规划与分析 人力资源规划

LED屏模组偏色怎么办?

Dylan

LED显示屏 全彩LED显示屏 led显示屏厂家

2024年带大家一起深入了解堡垒机功能以及作用

行云管家

网络安全 数据安全 云安全 堡垒机

深入可观测底层:OpenTelemetry 链路传递核心原理_语言 & 开发_蒋志伟_InfoQ精选文章