一个完整的分布式追踪系统是什么样子的

一个完整的分布式追踪系统是什么样子的

现代分布式链路追踪公认的起源,是 Google 在 2010 年发表的论文《Dapper : a Large-Scale Distributed Systems Tracing Infrastructure》,这篇论文介绍了 Google 从 2004 年开始使用的分布式追踪系统 Dapper 的实现原理。

此后,所有业界有名的追踪系统,无论是国外 Twitter 的Zipkin、Naver 的Pinpoint(Naver 是 Line 的母公司,Pinpoint 的出现其实早于 Dapper 论文的发表,在 Dapper 论文中还提到了 Pinpoint),还是国内阿里的鹰眼、大众点评的CAT、个人开源的SkyWalking(后来进入 Apache 基金会孵化毕业),都受到了 Dapper 论文的直接影响。

那么,从广义上讲,一个完整的分布式追踪系统,应该由数据收集、数据存储和数据展示三个相对独立的子系统构成;而从狭义上讲,则就只是特指链路追踪数据的收集部分。比如Spring Cloud Sleuth就属于狭义的追踪系统,通常会搭配 Zipkin 作为数据展示,搭配 Elasticsearch 作为数据存储来组合使用。

而前面提到的那些 Dapper 的徒子徒孙们,就大多都属于广义的追踪系统,它们通常也被称为“APM 系统”(Application Performance Management,应用性能管理)。

追踪与跨度

为了有效地进行分布式追踪,Dapper 提出了“追踪”与“跨度”两个概念。

从客户端发起请求抵达系统的边界开始,记录请求流经的每一个服务,直到向客户端返回响应为止,这整个过程就叫做一次“追踪”(Trace,为了不产生混淆,我后面就直接使用英文 Trace 来指代了)。

由于每次 Trace 都可能会调用数量不定、坐标不定的多个服务,那么为了能够记录具体调用了哪些服务,以及调用的顺序、开始时点、执行时长等信息,每次开始调用服务前,系统都要先埋入一个调用记录,这个记录就叫做一个“跨度”(Span)。

Span 的数据结构应该足够简单,以便于能放在日志或者网络协议的报文头里;也应该足够完备,起码要含有时间戳、起止时间、Trace 的 ID、当前 Span 的 ID、父 Span 的 ID 等能够满足追踪需要的信息。

事实上,每一次 Trace 都是由若干个有顺序、有层级关系的 Span 所组成一颗“追踪树”(Trace Tree),如下图所示:

1

那么这样来看,我们就可以从下面两个角度来观察分布式追踪的特征:

扫描二维码关注公众号,回复: 14924084 查看本文章

从目标来看,链路追踪的目的是为排查故障和分析性能提供数据支持,系统在对外提供服务的过程中,持续地接受请求并处理响应,同时持续地生成 Trace,按次序整理好 Trace 中每一个 Span 所记录的调用关系,就能绘制出一幅系统的服务调用拓扑图了。

其实这个用时序图(顺序图)表示会更为清晰

uml使用那一片文章写过开发人员应该掌握的画图能力
2

这样,根据拓扑图中 Span 记录的时间信息和响应结果(正常或异常返回),我们就可以定位到缓慢或者出错的服务;然后,将 Trace 与历史记录进行对比统计,就可以从系统整体层面分析服务性能,定位性能优化的目标。

从实现来看,为每次服务调用记录 Trace 和 Span,并以此构成追踪树结构,看起来好像也不是很复杂。然而考虑到实际情况,追踪系统在功能性和非功能性上都有不小的挑战。

功能上的挑战来源于服务的异构性,各个服务可能会采用不同的程序语言,服务间的交互也可能会采用不同的网络协议,每兼容一种场景,都会增加功能实现方面的工作量。

而且想要做成一个成型产品,还要加上每个节点,不同环境请求重试复现。

而非功能性的挑战,具体就来源于以下这四个方面:

  • 低性能损耗:分布式追踪不能对服务本身产生明显的性能负担。追踪的主要目的之一就是为了寻找性能缺陷,越慢的服务就越是需要追踪,所以工作场景都是性能敏感的地方。
  • 随应用扩缩:现代的分布式服务集群都有根据流量压力自动扩缩的能力,这就要求当业务系统扩缩时,追踪系统也能自动跟随,不需要运维人员人工参与。
  • 持续的监控:即要求追踪系统必须能够 7x24 小时工作,否则就难以定位到系统偶尔抖动的行为。

所以总而言之,分布式追踪的主要需求是如何围绕着一个服务调用过程中的 Trace 和 Span,来低损耗、高透明度地收集信息,不管是狭义还是广义的链路追踪系统,都要包含数据收集的工作,这是可以说是追踪系统的核心。那么接下来,我们就来了解下三种主流的数据收集方式。

目前,追踪系统根据数据收集方式的差异,可以分为三种主流的实现方式,分别是基于日志的追踪(Log-Based Tracing),基于服务的追踪(Service-Based Tracing)和基于边车代理的追踪(Sidecar-Based Tracing)。

基于日志的追踪

基于日志的追踪思路是将 Trace、Span 等信息直接输出到应用日志中,然后随着所有节点的日志归集过程汇聚到一起,再从全局日志信息中反推出完整的调用链拓扑关系。日志追踪对网络消息完全没有侵入性,对应用程序只有很少量的侵入性,对性能的影响也非常低。

但这种实现方式的缺点是直接依赖于日志归集过程,日志本身不追求绝对的连续与一致,这就导致了基于日志的追踪,往往不如其他两种追踪实现来的精准。

还有一个问题是,由于业务服务的调用与日志的归集并不是同时完成的,也通常不由同一个进程完成,有可能发生业务调用已经顺利结束了,但由于日志归集不及时或者精度丢失,导致日志出现延迟或缺失记录,进而产生追踪失真的情况。这也正是我在上节课介绍 Elastic Stack 时提到的观点,ELK 在日志、追踪和度量方面都可以发挥作用,这对中小型应用确实能起到一定的便利作用,但对于大型系统来说,最好还是由专业的工具来做专业的事。

日志追踪的代表产品是 Spring Cloud Sleuth,下面是一段由 Sleuth 在调用时自动生成的日志记录,你可以从中观察到 TraceID、SpanID、父 SpanID 等追踪信息。


# 以下为调用端的日志输出:
Created new Feign span [Trace: cbe97e67ce162943, Span: bb1798f7a7c9c142, Parent: cbe97e67ce162943, exportable:false]
2019-06-30 09:43:24.022 [http-nio-9010-exec-8] DEBUG o.s.c.s.i.web.client.feign.TraceFeignClient - The modified request equals GET http://localhost:9001/product/findAll HTTP/1.1

X-B3-ParentSpanId: cbe97e67ce162943
X-B3-Sampled: 0
X-B3-TraceId: cbe97e67ce162943
X-Span-Name: http:/product/findAll
X-B3-SpanId: bb1798f7a7c9c142

# 以下为服务端的日志输出:
[findAll] to a span [Trace: cbe97e67ce162943, Span: bb1798f7a7c9c142, Parent: cbe97e67ce162943, exportable:false]
Adding a class tag with value [ProductController] to a span [Trace: cbe97e67ce162943, Span: bb1798f7a7c9c142, Parent: cbe97e67ce162943, exportable:false]

基于服务的追踪

基于服务的追踪是目前最为常见的追踪实现方式,被 Zipkin、SkyWalking、Pinpoint 等主流追踪系统广泛采用。服务追踪的实现思路是通过某些手段给目标应用注入追踪探针(Probe),比如针对 Java 应用,一般就是通过 Java Agent 注入的。

探针在结构上可以看作是一个寄生在目标服务身上的小型微服务系统,它一般会有自己专用的服务注册、心跳检测等功能,有专门的数据收集协议,可以把从目标系统中监控得到的服务调用信息,通过另一次独立的 HTTP 或者 RPC 请求,发送给追踪系统。

因此,基于服务的追踪会比基于日志的追踪消耗更多的资源,也具有更强的侵入性,而换来的收益就是追踪的精确性与稳定性都有所保证,不必再依靠日志归集来传输追踪数据。

这里我放了一张 Pinpoint 的追踪效果截图,从图中可以看到参数、变量等相当详细的方法级调用信息。不知道你还记不记得,在上节课“日志分析”里,其实可以把“打印追踪诊断信息”需要诊断方法参数、返回值、上下文信息,或者方法调用耗时这类数据,通过追踪系统来实现,会是比通过日志系统实现更加恰当的解决方案。

3

另外,我也必须给你说明清楚,像图例中的 Pinpoint 这种详细程度的追踪,对应用系统的性能压力是相当大的,一般仅在除错时开启,而且 Pinpoint 本身就是比较重负载的系统(运行它必须先维护一套 HBase),这其实就严重制约了它的适用范围。

有的公司会选择在预发环境放置全链路追踪,流量较少性能损失和机器成本可以接受,线上出问题的case使用请求参数复现,可以一定程度平衡成本性能和需求。

目前服务追踪的其中一个发展趋势是轻量化,国产的 SkyWalking 正是这方面的佼佼者。

基于边车代理的追踪

基于边车代理的追踪是服务网格的专属方案(Service Mesh 跟k8s的pod一样自己维护一个代理,负责网络等与业务无关的部分,业务部分交给pod,他们整体作为一个服务提供者对外提供访问),也是最理想的分布式追踪模型,它对应用完全透明,无论是日志还是服务本身,都不会有任何变化;它与程序语言无关,无论应用是采用什么编程语言来实现的,只要它还是通过网络(HTTP 或者 gRPC)来访问服务,就可以被追踪到;它也有自己独立的数据通道,追踪数据通过控制平面进行上报,避免了追踪对程序通信或者日志归集的依赖和干扰,保证了最佳的精确性。

而如果要说这种追踪实现方式还有什么缺点的话,那就是服务网格现在还不够普及。当然未来随着云原生的发展,相信它会成为追踪系统的主流实现方式之一。

还有一点就是,边车代理本身对应用透明的工作原理,决定了它只能实现服务调用层面的追踪,像前面 Pinpoint 截图那样的本地方法调用级别的追踪诊断,边车代理是做不到的。(其实一般情况服务间数据就足够诊断了)

现在,市场占有率最高的边车代理Envoy就提供了相对完善的追踪功能,但没有提供自己的界面端和存储端,所以 Envoy 和 Sleuth 一样,都属于狭义的追踪系统,需要配合专门的 UI 与存储来使用。SkyWalking、Zipkin、Jaeger、LightStep Tracing等系统,现在都可以接受来自于 Envoy 的追踪数据,充当它的界面端

不过,虽然链路追踪在数据的收集这方面,已经有了几种主流的实现方式,但各种追踪产品通常并不互通。接下来我们就具体看看追踪在行业标准与规范方面存在的问题。

追踪规范化

CNCF 技术委员会发布了OpenTracing 和谷歌和微软推出了 OpenCensus 这两个竞品,2019年又忽然宣布握手言和,它们共同发布了可观测性的终极解决方案OpenTelemetry,并宣布会各自冻结 OpenTracing 和 OpenCensus 的发展。

OpenTelemetry 的野心很大,它不仅包括了追踪规范,还包括了日志和度量方面的规范、各种语言的 SDK,以及采集系统的参考实现。距离一个完整的追踪与度量系统,只是差了一个界面端和指标预警这些会与用户直接接触的后端功能,OpenTelemetry“大度”地把它们留给具体产品去实现,勉强算是没有对一众 APM 厂商赶尽杀绝,留了一条活路。

不过,OpenTelemetry 毕竟是 2019 年才出现的新生事物,尽管背景渊源深厚,前途光明,但未来究竟如何发展,能否打败现在已有的众多成熟系统,目前仍然言之尚早。

小结

这节课,我给你介绍了分布式追踪里“追踪”与“跨度”两个概念,要知道目前几乎所有的追踪工具都是围绕这两个 Dapper 提出的概念所设计的,因此理解它们的含义,对你使用任何一款追踪工具都会有帮助。而在理论之外,我还讲解了三种追踪数据收集的实现方式,分别是基于日志、基于服务、基于边车代理的追踪,你可以重点关注下这几种方式各自的优势和缺点,以此在工作实践中选择合适的追踪方式。

猜你喜欢

转载自blog.csdn.net/wdays83892469/article/details/129871948