微服务监控 - 分布式追踪(Distributed Tracing)

原文:https://makeoptim.com/distributed-tracing/open-tracing

正如互联网架构演进所讲的,微服务给我们带来许多好处,但同时也带来许多问题,微服务的监控便是其中之一。

metrics-tracing-and-logging 文中提到微服务监控包括日志、指标、追踪,三者相辅相成。

image
  • 指标:其特点是可累加的,具有原子性,都是一个逻辑计量单元,或者一个时间段内的柱状图。 例如:队列的当前深度可以被定义为一个计量单元,在写入或读取时被更新统计; 输入 HTTP 请求的数量可以被定义为一个计数器,用于简单累加; 请求的执行时间可以被定义为一个柱状图,在指定时间片上更新和统计汇总。

  • 日志:其特点是描述一些离散的(不连续的)事件。 例如:应用通过一个滚动的文件输出 debug 或 error 信息,并通过日志收集系统,存储到 Elasticsearch 中; 审批明细信息通过 Kafka,存储到数据库(BigTable)中; 又或者,特定请求的元数据信息,从服务请求中剥离出来,发送给一个异常收集服务,如 NewRelic。

  • 追踪:其特点是在单次请求的范围内,处理信息。 任何的数据、元数据信息都被绑定到系统中的单个事务上。 例如:一次调用远程服务的 RPC 执行过程;一次实际的 SQL 查询语句;一次 HTTP 请求的业务性 ID。

在前面的文章中,我们通过 EFK 收集日志,对系统和各个服务的运行状态进行监控;通过 Prometheus 收集指标(Metrics),对系统和各个服务的性能进行监控;通过 Grafana 可视化指标并及时警报

本篇开始,将为大家带来分布式追踪,以此来完善微服务监控系统

分布式追踪

分布式追踪可以通过对微服务调用链的跟踪,构建一个从服务请求开始到各个微服务交互的全部调用过程的视图。用户可以从中了解到诸如应用调用的时延网络调用(HTTP,RPC)的生命周期系统的性能瓶颈等等信息。

image

谷歌在 2010 年发表的论文 《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》 中介绍了分布式追踪的概念。

主要概念:

追踪(Trace)

就是由分布的微服务协作所支撑的一个事务。一个追踪,包含为该事务提供服务的各个服务请求

跨度(Span)

Span 是事务中的一个工作流,一个 Span 包含了时间戳,日志和标签信息。Span 之间包含父子关系,或者主从(Followup)关系

跨度上下文(Span Context)

跨度上下文是支撑分布式追踪的关键,它可以在调用的服务之间传递,上下文的内容包括诸如:从一个服务传递到另一个服务的时间,追踪的 ID,Span 的 ID 还有其它需要从上游服务传递到下游服务的信息。

OpenTracing

随着分布式追踪的流行,各家都发布了自己的产品和设计,这导致无规范可言

为了解决这个问题,OpenTracing 基于谷歌提出的概念定义了一个开放的分布式追踪的规范。OpenTracing 通过提供平台无关、厂商无关的 API,为分布式追踪提供统一的概念数据标准,使得开发人员能够方便的添加(或更换)追踪系统的实现。

注:以下篇幅为 The OpenTracing Semantic Specification 的部分翻译。

数据模型

OpenTracing 中的 Traces 由其 Span 隐式定义。Trace 可被认为是由一组 Span 定义的有向无环图(DAG),在 Span 之间的关系被称为 References。

以下是一个由 8 个 Span 构成的 Trace 的例子:

Causal relationships between Spans in a single Trace


        [Span A]  ←←←(the root span)
            |
     +------+------+
     |             |
 [Span B]      [Span C] ←←←(Span C is a `ChildOf` Span A)
     |             |
 [Span D]      +---+-------+
               |           |
           [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                       ↑
                                       ↑
                                       ↑
                         (Span G `FollowsFrom` Span F)

有时用时间轴来显示 Traces 更方便,如下图所示:

Temporal relationships between Spans in a single Trace


––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

 [Span A···················································]
   [Span B··············································]
      [Span D··········································]
    [Span C········································]
         [Span E·······]        [Span F··] [Span G··] [Span H··]

每个 Span 封装了如下状态:

  • 操作名称
  • 开始时间戳
  • 结束时间戳
  • 一组零或多个键:值结构的 Span 标签(Tags)键必须是字符串可以是字符串,布尔或数值类型.
  • 一组零或多个 Span 日志(Logs),其中每个都是一个键:值映射并与一个时间戳配对键必须是字符串值可以是任何类型。 并非所有的 OpenTracing 实现都必须支持每种值类型。
  • 一个 SpanContext(见下文)
  • 零或多个因果相关的 Span 间的 References (通过那些相关的 Span 的 SpanContext)

每个 SpanContext 封装了如下状态:

  • 任何需要跟跨进程 Span 关联的,依赖于 OpenTracing 实现的状态(例如 Trace 和 Span 的 id)
  • 键:值结构的跨进程Baggage Items

Span 间的 Reference

一个 Span 可引用零或多个因果相关的 SpanContext。OpenTracing 目前定义了两种类型的 Reference: ChildOfFollowsFrom。这两种 Reference 类型都对父子 Span 间的因果关系进行了建模。未来,OpenTracing 可能会为不具因果关系的 Span 提供不同类型的 Reference (例如批量的 Span,卡在队列中的 Span 等)。

ChildOf reference: 一个 Span 可以是另一个 Span 的子 Span。在 ChildOf 引用中,父 Span 在某种程度上取决于子 Span。下列情况会构成 ChildOf 关系:

  • 在一个 RPC 中,代表服务端的 Span 可作为 ChildOf 代表客户端的 Span
  • 在一个持久化进程中,代表 SQL 插入的 Span 可作为 ChildOf 代表 ORM save 方法的 Span
  • 多个并发(可能是分布式)执行任务的 Span 可能分别各自为 Childof 一个合并了多个子 Span 结果的父 Span

下列这些都是有效的具有 ChildOf 关系的时序图

    [-Parent Span---------]
         [-Child Span----]

    [-Parent Span--------------]
         [-Child Span A----]
          [-Child Span B----]
        [-Child Span C----]
         [-Child Span D---------------]
         [-Child Span E----]

FollowsFrom reference: 有些父 Span 不依赖于任何子 Span 的结果。这种情况下,我们仅认为子 Span 在因果上 FollowsFrom 父 Span。有许多不同的 FollowsFrom 引用子类别,在 OpenTracing 的未来版本中,它们可能会被更正式地区分。

下列这些都是有效的具有 FollowsFrom 关系的时序图

    [-Parent Span-]  [-Child Span-]


    [-Parent Span--]
     [-Child Span-]


    [-Parent Span-]
                [-Child Span-]

接口定义

OpenTracing 规范中有三种相互关联的关键类型 Tracer,Span 和 SpanContext。接下来,我们来看一下各种类型的行为; 简单来说,每种行为分别在编程语言中对应为一个”方法”,尽管实际上可能是一组重载方法。

当我们讨论“可选”参数时,应当清楚,不同的语言有不同的方式来解释这个概念。比如说,在 Go 语言中我们会使用 “functional Options” 这个术语,而在 Java 中我们会使用 builder 模式。

Tracer

Tracer 接口创造 Span 并且能够跨进程地 Inject (序列化)和 Extract (反序列化)。严格来说,它应具有以下能力:

启动一个新的 Span

必要参数

  • 操作名称,一个人工可读的字符串,它简洁地表示由 Span 完成的工作 (例如,RPC 方法名称、函数名称或一个较大的计算任务中的阶段的名称)。操作名称应该用泛化的字符串形式标识出一个 Span 实例. 也就是说,get_userget_user/314159 好。

比如说这里有几个操作名称,用于得到一个虚构的账户信息:

操作名称 建议
get 太宽泛
get_account/792 太具体
get_account 刚刚好, account_id=792 可作为一个合适的 Span tag

可选参数

  • 零或多个与 SpanContext 相关的 references,尽可能包括 ChildOfFollowFrom 引用类型的信息
  • 开始时间戳,如果没有的话,默认使用现实时间(walltime)
  • 零或多个标签

返回值是一个刚刚启动的 Span 实例

注入(Inject) SpanContext 到载体中

必要参数

  • SpanContext 实例
  • 格式描述符(format descriptor)(通常但不一定是字符串常量),告诉 Tracer 的实现如何在载体对象中对 SpanContext 进行编码
  • 载体(carrier),其类型由格式描述符指定。Tracer 的实现将根据格式描述对此载体对象中的 SpanContext 进行编码

从载体中抽取(Extract) SpanContext

必要参数

  • 格式描述符(format descriptor),告诉 Tracer 的实现如何在载体对象中对 SpanContext 进行解码
  • 载体,其类型由格式描述符指定。 Tracer 的实现将根据格式描述对此载体对象中的 SpanContext 进行解码

返回值是一个 SpanContext 实例,适合作为通过 Tracer 启动 Span 时的 reference

注意: Injection 和 Extraction 所需的格式

Injection 和 Extraction 都依赖于一个可扩展的格式描述符参数,其指定了相关联的”载体”类型以及 SpanContext 是如何被编码的。Tracer 实现必须支持下列所有格式。

  • 文本映射(Text Map): 任意的无字符编码限制的 string-string 型键值结构
  • HTTP Headers: string-string 型的键值结构在 HTTP headers(RFC 7230)中也适用。在实操中,由于 HTTP header 很容易被各种不同的方式处理, 强烈建议在 Tracer 的实现中使用有限的键空间和保守的转意值
  • 二进制: 代表了 SpanContext 的任意二进制数据

Span

除了获取 SpanSpanContext 的方法之外,下面任何方法都不能在 Span 完成后被调用。

获取 Span 的 SpanContext

无需参数。

返回值SpanSpanContext 。返回值甚至可能在 Span 结束后被使用。

重写操作名

必要参数

  • 新的操作名,当 Span 启动时取代旧内容

结束 Span

可选参数

显式地添加 Span 的结束时间戳,如果没有这个参数则会隐式的添加现实时间(walltime)
除了获取 Span 的 SpanContext 的方法之外,任何 Span 对象的方法都不能在其完成后被调用。

设置 Span 标签

必要参数

  • 标签的键,必须是字符串
  • 标签的值,必须是字符串,布尔值,数值类型其中之一

注意 OpenTracing 项目文档中已经为一些"标准标签"规定了语义。

记录(Log) 结构化的数据

必要参数

  • 一到多个键值对,键必须是字符串,值可以是任意类型。有些 OpenTracing 的实现可以处理更多的日志的值。

可选参数

  • 显式时间戳。该参数必须落在当前 Span 的开始与结束时间戳之间。

注意 OpenTracing 项目文档中已经为一些"日志的标准键"规定了语义。

设置(set) baggage item

Baggage item 是对应于某个 Span 及其 SpanContext ,以及所有直接或间接引用自本地(local) SpanSpan 的键值对。也就是说,baggage items 是与其 trace 一起传播的

Baggage item 为全栈式的 OpenTracing 集成提供了强大的功能(比如在移动 App 上使用时,它可以一路追踪数据直至储存系统的深度),不过使用这些功能时要当心。

每个键值都会被拷贝到每一个本地(local)及远程的子 Span,这可能导致巨大的网络和 CPU 开销

必要参数

  • baggage 键,字符串
  • baggage 值,字符串

获取(get) baggage item

必要参数

  • baggage 键,字符串

返回值是对应的 baggage 值,或是一个表示没有 baggage 值的一个量。

SpanContext

在一般所指的 OpenTracing 层面上,SpanContext 更像是一个概念而不是一种实用功能。也就是说,这对 OpenTracing 的实现来说提供一层薄的接口是至关重要的。当启动一个 Span 或者注入/提取(injecting/extracting) trace 时,多数 OpenTracing 用户仅通过 reference 与 SpanContext 进行交互。

在 OpenTracing 中,SpanContext 被强制设定为不可变的(immutable),以应对在 Span 结束时和引用(reference)时产生的复杂的生命周期问题。

遍历所有的 baggage item

不同语言对此有不同的建模方式,不过给出一个 SpanContext 实例,语义上还是应该能让调用者在短时间内遍历 baggage items。

NoopTracer

所有语言的 OpenTracing API 必须提供某种 NoopTracer 实现,可用作标记控制(flag-control)或者进行一些用于测试的无害注入等。某些情况下(比如 Java),NoopTracer 可能在它自己的制品包(packaging artifact)中。

可选的 API 元素

有些语言提供了在单进程中用来传递活动的 SpanSpanContext 的工具。比如,opentracing-go 提供了在 Go 的 context.Context 机制中,用来 set 和 get 活动 Span 的帮助函数(helpers)

小结

本篇文章作为分布式追踪 的开篇,为大家介绍了分布式追踪的概念和作用,以及 OpenTracing 的出现和术语。希望能帮助大家对分布式追踪有个概念,下一篇将为大家介绍 CNCF 的分布式追踪方案 Jaeger

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容