写点什么

OpenTelemetry 系列 (二)|初探 OpenTelemetry

作者:骑牛上青山
  • 2022-12-12
    上海
  • 本文字数:3172 字

    阅读完需:约 10 分钟

前言

OpenTelemetry作为一个分布式追踪的项目,他支持非常多的语言,如 Java,Golang,Python 等,鉴于笔者的主力语言为 Java,并且后续需要介绍 OpenTelemetry 的 Java Agent 实现,所以后续文章中的相关知识点都以 Java 或者 Java Sdk 的方式为主。

初识 OpenTelemetry

在微服务广泛发展和使用的当下,对于整个微服务体系的使用情况的观察以及服务依赖调用情况都不再像以往那么清晰明了。而这正是 OpenTelemetry 能够为我们提供的能力。


OpenTelemetry 源自 OpenSencuc 和 OpenTracing 的合并,它的目标是集成 Trace,Metrics,Logging 能力来提供可观测性。过去的分布式追踪往往是各做各的,没有固定的标准,各个分布式追踪方案各显神通,使用不同的协议,不同的标准。但是 OpenTelemetry 不同,它提供了一系列的标准,并且他的可插拔式的架构为将来的协议和数据结构扩展提供了便利的方式。

调用链 Trace

分布式调用链,俗称调用链,用来记录请求的路径整体路径。下图是一个典型的的请求以及其 RPC 调用的链路:



从图中我们可以很清晰的了解到刚才的请求是怎么一个流转过程,经过了什么组件和服务,接口。这就是调用链的作用之一,让我们的请求链路更加透明清晰。

Span

Span 在调用链中是一个基础的单元,一个调用链是由很多的 Span 组成的。在一个 Span 中会包含如下信息:


  1. 名称

  2. 父 Span 的 ID,root 节点的父 Span 为空

  3. 开始与结束时间戳

  4. Span Context

  5. Attributes

  6. Span 事件

  7. Span Links

  8. Span 状态

  9. Span Kind


以下是一个典型的 Span 结构:


{  "trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",  "parent_id": "",  "span_id": "086e83747d0e381e",  "name": "/v1/sys/health",  "start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",  "end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",  "status_code": "STATUS_CODE_OK",  "status_message": "",  "attributes": {    "net.transport": "IP.TCP",    "net.peer.ip": "172.17.0.1",    "net.peer.port": "51820",    "net.host.ip": "10.177.2.152",    "net.host.port": "26040",    "http.method": "GET",    "http.target": "/v1/sys/health",    "http.server_name": "mortar-gateway",    "http.route": "/v1/sys/health",    "http.user_agent": "Consul Health Check",    "http.scheme": "http",    "http.host": "10.177.2.152:26040",    "http.flavor": "1.1"  },  "events": [    {      "name": "",      "message": "OK",      "timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"    }  ]}
复制代码

Span Context

Span Context 可以理解为上下文,是 Span 中包含的不可变的对象。在 Span Context 中包含了:


  • traceId:调用链 ID

  • spanId:Span 的 ID

  • traceFlag:二进制形式的调用链标志位,一般用于表示调用链是否采样(isSampled)

  • traceState:承载调用链信息的 K-V 结构列表

Attributes

Attributes 是一个用来携带信息的 K-V 结构。


在 java sdk 中可以通过:


Span.current().setAttribute("My Attributes", "attr");
复制代码



来自定义你想要设置的 Attributes。当然在 OpenTelemetry 中默认内置的那些Instrumentation都会有定义一些指定标准化的的 Attributes,详情可以参照Semantic Attributes

Span 事件

Span 事件(Events)是一种事件机制,可以将事件触发与具体的 Span 进行绑定,然后在调用链页面展示出来。如下事例:


Span.current().addEvent("My Event");
复制代码



Span Links

Span Links 是一种能够将调用链关联起来的技术,通过配置关联的 Span,可以在页面中展现关联的调用链信息。不过请注意 Span Links 必须要在 Span 创建时才能添加,不像 Events 和 Attributes 一样能在 Span 创建之后添加。例子如下:


Tracer tracer = GlobalOpenTelemetry.getTracer("1111");
Span span = tracer.spanBuilder("start") .addLink(SpanContext.create("ee868088dfd10adbaa459c9aa353b112", "53b11b6c55010604", TraceFlags.getDefault(), TraceState.getDefault())).startSpan();span.end();
复制代码



Span 状态

Span 状态(Status)是定义好的 Span 的状态,有如下几种:


  • Unset

  • Ok

  • Error

Span Kind

Span Kind 是指 Span 类型,有如下几种:


  • Server

  • Client

  • Producer

  • Consumer

  • Internal


顾名思义 Server/Client 指的是服务端/客户端,Producer/Consumer 指的是生产者/消费者,显然这个一般适用于消息队列,Internal 是内部组件产生的 Span

Trace 构建的原理

简单来说的话 Trace 是由众多的 Span 组成的,而 Span 则是由众多的 Instrumentation 库组成的,这些库由开源作者构建,用于支持不同的组件,如 http 请求,kafka,redis 等等。依托于这些 Instrumentation,调用链可以生成对应的 Span。


生成 Span 自然不是难题,问题在于是如何将这些 Span 串联起来的。在 Trace 中有一个唯一的标识 TraceID,而且在 Span 中也有一个 SpanId 和 ParentSpanId,借助这些信息,在 Span 将所有数据推送到服务端后,服务端就能根据这些信息进行重组,然后在界面上进行展示。


但是又存在一个问题,TraceId 以及 ParentSpanId 是如何在 Span 间进行传递的呢?


这里就涉及到了 Trace 的底层原理了。在这里以 Java Sdk 来举例。在 Sdk 中会定义一个 Context 类用于建立一个内存中的线程隔离的存储机制来存储上游传递的数据。一般来说上游往下游传递数据每个插件都是不同的形式。例如如果是 http 请求,那就借助 Header,如果是 Kafka,也是借助于 Kafka 自带的 prop 来进行数据传递。之后在下游获取到数据后利用 Context 将其存放入内存中,这个过程被称为extract,在数据要再往下传递时,需要将内存中数据取出,在解析成 Header 或是其他的形式,这个被称为inject。调用链信息正是以此来传递的。


Trace 就是依靠traceparent来进行传递的,traceparent不仅包含了 traceId,还包含了一些isSample等等的基础信息。

Metrics

Metrics 是一种度量标准,用于展现应用的 CPU,内存等等指标级的度量信息。


OpenTelemetry 定义了三种 metrics 仪器:


  1. counter: 累加值,这类指标不会减少,只会不断的累加上去

  2. measure: 一段时间的数据聚合值,表示的是一段时间内的数据累加值

  3. observer: 抓取当前时间的一系列特定值


实际上 OpenTelemetry 提供了许多基础的指标计算方式,例如:LongCounter,LongUpDownCounter,DoubleHistogram,DoubleGauge 等等。


Meter meter = GlobalOpenTelemetry.meterBuilder("my-meter-instrumentation")                .setInstrumentationVersion("1.0.0")                .build();
LongCounter counter = meter .counterBuilder("my_metrics") .setDescription("My Metrics") .setUnit("1") .build();
counter.add(100);
复制代码


上述代码是一个简单的创建指标的的例子,这里创建了一个固定值为 100 的名为my_metrics的指标,由于是 counter,所以最终指标名为my_metrics_total



Logs

日志也是 OpenTelemetry 的一大功能之一,不过截止到本文发布前,Logs 功能还未 GA,因此存在变数,后续我们在聊到 Agent 相关内容时会再简单聊聊这部分内容,在这里就先一笔带过。

Baggage

Baggage 用于在 Span 间传递数据。


设想一个场景,你希望在链路的当前的 Span 中将某些数据传递下去,使用 attributes 显示然是不行的,因此需要一些手段将其传递下去,Baggage 就是为此而设计的。


其实 Baggage 的原理基本和调用链的 traceId 的传递基本相似,不同之处是它定义了一个名为 baggage 的 key,而这个 key 中包含的值是以 K-V 形式组织的,因此你可以传递自己想要的值下去。


在早期 Baggage 底层维护了一个 Map 来存储这些数据,后来在某个版本后改成了用数组的形式,每两个数组位置分别存储一对 K-V,并且做了一些特殊的处理来实现删除等操作,有兴趣的可以去看看源码。

总结

在本文中我们简单的介绍了 OpenTelemetry 的一些使用和实现的原理,在后续的文章中会更多的介绍整个 OpenTelemetry 的体系,请期待后续!

参考文档:

[1] https://opentelemetry.io/docs


发布于: 2 小时前阅读数: 12
用户头像

还未添加个人签名 2021-05-18 加入

还未添加个人简介

评论

发布
暂无评论
OpenTelemetry系列 (二)|初探OpenTelemetry_Java_骑牛上青山_InfoQ写作社区