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

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会有如下可能的名称:

操作名

指导意见

get

太抽象

get_account/792

太明确

get_account

正确的操作名,关于account_id=792的信息应该使用Tag操作

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)形式的TagsTags是没有时间戳的,支持简单的对span进行注解和补充。

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

SpanContext

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

Baggage

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

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

Baggage vs. Span Tags

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

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

Inject and Extract

SpanContexts可以通过Injected操作向Carrier增加,或者通过ExtractedCarrier中获取,跨进程通讯数据(例如: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/opentracing-io/content/

https://www.jianshu.com/p/1231f5e1c883

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

{{o.name}}
{{m.name}}

猜你喜欢

转载自my.oschina.net/u/5148943/blog/5525989