likes
comments
collection
share

分布式数据库 Tracing (一)— Opentracing

作者站长头像
站长
· 阅读数 25

Part 1 - 为什么需要 Tracing当前微服务架构和分布式系统变得越来越流行,系统庞大且服务数量繁杂多样,甚至数据库也开始使用分布式架构。当一个生产系统面对真正的高并发,或者解耦成大量微服务时,以前很容易实现的重点任务变得困难了。开发过程中需要面临一系列问题:用户体验优化、后台真实错误原因分析,分布式系统内各组件的调用情况等。在这些问题的驱动下,Tracing 变成了分布式系统必不可少的组成部分。

Part 2 - Opentracing 的产生当代分布式跟踪系统(例如,Zipkin, Dapper, HTrace, X-Trace 等)旨在解决分布式系统下的用户体验优化、后台真实错误原因分析、分布式系统内各组件的调用情况等问题,但是他们使用不兼容的 API 来实现各自的应用需求。尽管这些分布式追踪系统有着相似的 API 语法,但各种语言的开发人员依然很难将他们各自的系统(使用不同的语言和技术)和特定的分布式追踪系统进行整合。

在这种背景下,opentracing 应运而生。Opentracing 通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统。Opentracing 提供了用于运营支撑系统的和针对特定平台的辅助程序库。

Part 3 - 什么是一个 trace在广义上,一个 trace 代表了一个事务或者流程在(分布式)系统中的执行过程。在 Opentracing 标准中,trace 是多个 span 组成的一个有向无环图(DAG),每一个 span 代表 trace 中被命名并计时的连续性的执行片段。

分布式追踪中的每个组件都包含自己的一个或者多个 span。例如,在一个常规的 RPC 调用过程中,Opentracing 推荐在 RPC 的客户端和服务端,至少各有一个 span,用于记录 RPC 调用的客户端和服务端信息。

一个父级的 span 会显示的并行或者串行启动多个子 span。在 Opentracing 标准中,甚至允许一个子 span 有个多父 span(例如:并行写入的缓存,可能通过一次刷新操作写入动作)。

Part 4 - Opentracing 概念与术语Traces一个 trace 代表一个潜在的,分布式的,存在并行数据或并行执行轨迹(潜在的分布式、并行)的系统。一个 trace 可以认为是多个 span 的有向无环图(DAG)。

Spans一个 span 代表系统中具有开始时间和执行时长的逻辑运行单元。span 之间通过嵌套或者顺序排列建立逻辑因果关系。

Operation Names每一个 span 都有一个操作名称,这个名称简单,并具有可读性高。(例如:一个 RPC 方法的名称,一个函数名,或者一个大型计算过程中的子任务或阶段)。span 的操作名应该是一个抽象、通用的标识,能够明确的、具有统计意义的名称;更具体的子类型的描述,请使用 Tags。

例如,假设一个获取账户信息的 span 会有如下可能的名称:分布式数据库 Tracing (一)— Opentracing

Inter-Span References

一个 span 可以和一个或者多个 span 间存在因果关系。Opentracing 定义了两种关系:ChildOf 和 FollowsFrom。这两种引用类型代表了子节点和父节点间的直接因果关系。未来,Opentracing 将支持非因果关系的 span 引用关系。(例如:多个 span 被批量处理,span 在同一个队列中,等等)

ChildOf 引用: 一个 span 可能是一个父级 span 的孩子,即 "ChildOf" 关系。在 "ChildOf" 引用关系下,父级 span 某种程度上取决于子 span。下面这些情况会构成 "ChildOf" 关系:

  • 一个 RPC 调用的服务端的 span,和 RPC 服务客户端的 span 构成 ChildOf 关系
  • 一个 sql insert 操作的 span,和 ORM 的 save 方法的 span 构成 ChildOf 关系

很多 span 可以并行工作(或者分布式工作)都可能是一个父级的 span 的子项,他会合并所有子 span 的执行结果,并在指定期限内返回

FollowsFrom 引用: 一些父级节点不以任何方式依赖他们子节点的执行结果,这种情况下,我们说这些子 span 和父 span 之间是 "FollowsFrom" 的因果关系。"FollowsFrom" 关系可以被分为很多不同的子类型,未来版本的 Opentracing 中将正式的区分这些类型

Logs每个 span 可以进行多次 Logs 操作,每一次 Logs 操作,都需要一个带时间戳的时间名称,以及可选的任意大小的存储结构。

标准中定义了一些日志(logging)操作的一些常见用例和相关的 log 事件的键值,可参考 Data Conventions Guidelines 数据约定指南。

Tags每个 span 可以有多个键值对(key:value)形式的 Tags,Tags 是没有时间戳的,支持简单的对 span 进行注解和补充。

和使用 Logs 的场景一样,对于应用程序特定场景已知的键值对 Tags,tracer 可以对他们特别关注一下。更多信息,可参考 Data Conventions Guidelines 数据约定指南。

SpanContext每个 span 必须提供方法访问 SpanContext。SpanContext 代表跨越进程边界,传递到下级 span 的状态。(例如,包含 <trace_id,span_id, sampled> 元组),并用于封装 Baggage (关于 Baggage 的解释,请参考下文)。SpanContext 在跨越进程边界,和在追踪图中创建边界的时候会使用。(ChildOf 关系或者其他关系,参考 Span 间关系 )。

BaggageBaggage 是存储在 SpanContext 中的一个键值对 (SpanContext) 集合。它会在一条追踪链路上的所有 span 内全局传输,包含这些 span 对应的 SpanContexts。在这种情况下,"Baggage" 会随着 trace 一同传播,他因此得名(Baggage 可理解为随着 trace 运行过程传送的 Baggage)。鉴于全栈 Opentracing 集成的需要,Baggage 通过透明化的传输任意应用程序的数据,实现强大的功能。例如:可以在最终用户的手机端添加一个 Baggage 元素,并通过分布式追踪系统传递到存储层,然后再通过反向构建调用栈,定位过程中消耗很大的 SQL 查询语句。

Baggage 拥有强大功能,但同时也会有很大的消耗。由于 Baggage 的全局传输,如果包含的数量太大,或者元素太多,它将降低系统的吞吐量或增加 RPC 的延迟。

Baggage vs. Span TagsBaggage 在全局范围内,(伴随业务系统的调用)跨进程传输数据。Span 的 tag 不会进行传输,因为他们不会被子级的 span 继承。

span 的 tag 可以用来记录业务相关的数据,并存储于追踪系统中。实现 Opentracing 时,可以选择是否存储 Baggage 中的非业务数据,Opentracing 标准不强制要求实现此特性。

Inject and ExtractSpanContexts 可以通过 Injected 操作向 Carrier 增加,或者通过 Extracted 从 Carrier 中获取,跨进程通讯数据(例如:HTTP 头)。通过这种方式,SpanContexts 可以跨越进程边界,并提供足够的信息来建立跨进程的 span 间关系(因此可以实现跨进程连续追踪)。

Part 5 - Opentracing 的使用使用 opentracing 必须实现如下接口。

Span 接口Span 接口必须实现以下功能:

GetContext:获取 span 上下文(即使 span 已经结束,或者即将结束,也可以获取)。

Finish:完成已经开始的 span。除了获取 span 上下文之外,Finish 必须是 span 实例最后被调用的方法。

SetTag:为 span 设置标签。key 必须是 string 类型;value 必须是 string、boolean 或者数值类型。

Log:增加一个日志事件。事件名称是 string 类型,参数值可以是任何类型、任何大小。实现不一定保存参数值(可能只保存格式化后的日志字符串)。

SetBaggageItem:设置一个 string:string 类型的键值对,会传递到未来的子级 span 中。

BaggageItem:获取 Baggage 中的元素。

SpanContext 接口用户可以通过 span 实例获取 SpanContext,或者使用提取 (Extract) 操作从 Tracer 实例中获取 SpanContext。

SpaContext 必须实现:

ForeachBaggageItem:迭代所有 Baggage 元素。

Tracer 接口必须实现以下功能:

StartSpan:创建新的 span。调用者可以指定一个或者多个 span 间的关系、一个显式指定的开始时间戳,以及标签集。

Injectspan context:用于跨进程传递数据,必须指定 Carrier。

Extractspan context:从 Carrier 中取出 span 上下文,可用于创建新的子级 span。(注意:有些实现中,span 在 RPC 两端具有相同的 ID;而另一些实现中,客户端是父级 span,服务端是子级 span。)

全局和空Tracer每个实现必须提供一个 no-op Tracer:其实现必须不会出错(包括传递 Baggage),不会有任何副作用。在其上进行注入操作永远返回成功;进行提取操作,则返回找不到 span 上下文。

Tracer 必须提供 no-no Span 实现:监控代码不依赖 Tracer 关于 Span 的返回值。

实现可能支持配置 (InitGlobalTracer ())和获取 (GlobalTracer ()) 全局的单例 Tracer。如果支持获取全局单例 Tracer,默认必须返回 no-op Tracer。

参考:

https://wu-sheng.gitbooks.io/...

https://www.jianshu.com/p/123...

https://zhuanlan.zhihu.com/p/...