链路追踪
当代互联网服务,通常都是用复杂,大规模分布式集群来实现,微服务化,这些软件模块分布在不同的机器,不同的数据中心,由不同团队,语言开发而成。因此,需要工具帮助理解,分析这些系统、定位问题,做到追踪每一个请求的完整调用链路,收集性能数据,反馈到服务治理中,链路追踪系统应运而生。
现有大部分 APM(Application Performance Management) 理论模型大多借鉴 google dapper 论文,Twitter的Zipkin,Uber的Jaeger,淘宝的鹰眼,大众的CAT,京东的Hydra等。
微服务问题:
- 故障定位难
- 链路梳理难
- 容量预估难
举个例子,一个场景下,一个请求进来,入口服务是 serviceA, serviceA 接到请求后访问数据库读取用户数据,然后向 serviceB 发起 rpc,serviceB 收到 rpc 请求时同时向后端服务 serviceC 和 serviceD 发起请求,等待请求回复后再返回 serviceA 的 rpc 调用。如果我们发现发起的请求失败,或者请求的时延很大,我们该如何去定位呢?
基于这个需求,我们将服务介入追踪系统。
分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点,数据存储、查询展示
在数据采集过程,需要侵入用户代码做埋点,不同系统的API不兼容会导致切换追踪系统需要做很大的改动。为了解决这个问题,诞生了opentracing 规范。
代码语言:javascript复制 ------------- --------- ---------- ------------
| Application | | Library | | OSS | | RPC/IPC |
| Code | | Code | | Services | | Frameworks |
------------- --------- ---------- ------------
| | | |
| | | |
v v v v
-----------------------------------------------------
| · · · · · · · · · · OpenTracing · · · · · · · · · · |
-----------------------------------------------------
| | | |
| | | |
v v v v
----------- ------------- ------------- -----------
| Tracing | | Logging | | Metrics | | Tracing |
| System A | | Framework B | | Framework C | | System D |
----------- ------------- ------------- -----------
OpenTracing
opentracing (中文)是一套分布式追踪协议,与平台,语言无关,统一接口,方便开发接入不同的分布式追踪系统。
- 语义规范 : 描述定义的数据模型 Tracer,Sapn 和 SpanContext 等;
- 语义惯例 : 罗列出 tag 和 logging 操作时,标准的key值;
Trace 和 sapn
在 OpenTracing 中,跟踪信息被分为 Trace和Span 两个部分,它们按照一定的结构存储跟踪信息,所以它们是 OpenTracing 中数据模型的核心。
Trace 是一次完整的跟踪,Trace 由多个 Span 组成。下图是一个 Trace 示例,由 8 个 Span 组成。
代码语言:javascript复制 [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)
Tracing: a Trace can be thought of as a directed acyclic graph (DAG) of Spans。
有点难翻译,大概意思是 Trace 是多个 Span 组成的有向非循环图。
在上面的示例中,一个 Trace 经过了 8 个服务,A -> C -> F -> G 是有严格顺序的,但是从时间上来看,B 、C 是可以并行的。为了准确表示这些 Span 在时间上的关系,我们可以用下图表示:
代码语言:javascript复制––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
有个要注意的地方, 并不是 A -> C -> F 表示 A 执行结束,然后 C 开始执行,而是 A 执行过程中,依赖 C,而 C 依赖 F。因此,当 A 依赖 C 的过程完成后,最终回到 A 继续执行。所以上图中 A 的跨度最大。
Span 格式
要深入学习,就必须先了解 Span对应的json数据: https://github.com/whuanle/DistributedTracing/issues/1
Trace
一个简化的 Trace 如下:
注:不同编程语言的字段名称有所差异,gRPC 和 Restful API 的格式也有所差异。
代码语言:javascript复制 "traceID": "790e003e22209ca4",
"spans":[...],
"processes":{...}
前面说到,在 OpenTracing 中,Trace 是一个有向非循环图,那么 Trace 必定有且只有一个起点。
这个起点会创建一个 Trace 对象,这个对象一开始初始化了 trace id 和 process,trace id 是一个 32 个长度的字符串组成,它是一个时间戳,而 process 是起点进程所在主机的信息。
每个Span封装了如下状态:
- 操作名称
- 开始时间戳
- 结束时间戳
- 一组零或多个键:值结构的 Span标签 (Tags)。键必须是字符串。值可以是字符串,布尔或数值类型.
- 一组零或多个 Span日志 (Logs),其中每个都是一个键:值映射并与一个时间戳配对。键必须是字符串,值可以是任何类型。 并非所有的 OpenTracing 实现都必须支持每种值类型。
- 一个 SpanContext (见下文)
- 零或多个因果相关的 Span 间的 References (通过那些相关的 Span 的 SpanContext )
每个 SpanContext 封装了如下状态:
- 任何需要跟跨进程 Span 关联的,依赖于 OpenTracing 实现的状态(例如 Trace 和 Span 的 id)
- 键:值结构的跨进程的 Baggage Items(区别于 span tag,baggage 是全局范围,在 span 间保持传递,而tag 是 span 内部,不会被子 span 继承使用。)
Inject 和 Extract 操作
既然是分布式的追踪,那肯定涉及到跨进程/机器通讯,在进程间通过传递 Spancontext 来提供足够的信息建立 span 间的关系。
在上游服务中,SpanContext 通过 Inject 操作向 Carrier 中注入tracing信息,传递后在下游服务中通过 Extract 从 Carrier 中提取tracing数据。
关于inject 和 extract
Sampling 采样
OpenTracing API 不强调采样的概念,但是大多数追踪系统通过不同方式实现采样。有些情况下,应用系统需要通知追踪程序,这条特定的调用需要被记录,即使根据默认采样规则,它不需要被记录。sampling.priority tag 提供这样的方式。追踪系统不保证一定采纳这个参数,但是会尽可能的保留这条调用。 sampling.priority - integer
- 如果大于 0, 追踪系统尽可能保存这条调用链
- 等于 0, 追踪系统不保存这条调用链
- 如果此tag没有提供,追踪系统使用自己的默认采样规则
OpenTracing 多语言支持
提供不同语言的 API,用于在自己的应用程序中执行链路记录。
OpenTracing API支持以下平台:
- Go - https://github.com/opentracing/opentracing-go
- Python - https://github.com/opentracing/opentracing-python
- Javascript - https://github.com/opentracing/opentracing-javascript
- Objective-C - https://github.com/opentracing/opentracing-objc
- Java - https://github.com/opentracing/opentracing-java
- C - https://github.com/opentracing/opentracing-cpp
参考资料:
https://wu-sheng.gitbooks.io/opentracing-io/content/
https://opentracing-contrib.github.io/opentracing-specification-zh/
https://opentracing.io/specification/