在分布式系统架构下,我们依据业务逻辑进行细致拆解,将其划分为多个独立的服务单元,并对外提供标准化的服务访问接口。在中大型系统场景中,一个接口功能的实现往往需要众多服务之间紧密协作、相互配合。
随着业务规模的持续拓展与业务复杂度的不断攀升,服务之间的相互调用关系呈现出日益复杂的态势,这不仅增加了系统的维护难度,也对系统的稳定性和性能提出了更高的挑战 。
从这张分布式服务调用链路的示意图中,你也可以看到,随着服务数量的不断增加,整个调用链路的分析工作变得越来越复杂。
显然,通过人工手段已经无法完成这种服务调用链路的分析。这时候,我们就需要引入分布式服务跟踪机制,并借助于一定的工具,来实现微服务架构下的服务监控。
什么是链路追踪?
分布式链路追踪就是将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
链路跟踪主要功能:
- 故障快速定位: 可以通过调用链结合业务日志快速定位错误信息。
- 链路性能可视化: 各个阶段链路耗时、服务依赖关系可以通过可视化界面展现出来。
- 链路分析: 通过分析链路耗时、服务依赖关系可以得到用户的行为路径,汇总分析应用在很多业务场景。
分布式服务跟踪基本原理
链路追踪系统(可能)最早是由Goggle公开发布的一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》被大家广泛熟悉,所以各位技术大牛们如果有黑武器不要藏起来赶紧去发表论文吧。
在这篇著名的论文中主要讲述了Dapper链路追踪系统的基本原理和关键技术点。接下来挑几个重点的技术点详细给大家介绍一下。
接下来,我们先来分析一下分布式服务跟踪机制的基本原理,这里,你需要先理解两个基本概念: Trace Id 和 Span Id。这两个概念是对服务跟踪最高层次的抽象,也是掌握分布式服务跟踪原理的基础。
Trace Id 和 Span Id
Trace Id
Trace Id 即跟踪 ID。在分布式架构中,每个请求会生成一个全局的唯一性 ID,通过这个 ID 可以串联起整个调用链,也就是说请求在分布式系统内部流转时,系统需要始终保持传递该唯一性 ID,直到请求返回。这个唯一性 ID 就是 Trace Id。
Span Id
Trace Id 代表的是整个调用链路,而针对链路中的各个组成部分,我们也需要引入新的概念。所以,除了 Trace Id 外,我们还需要 Span Id。
Span Id 一般被称为跨度 ID。所谓跨度,就是调用链路中的一段时间,有明确的开始和结束这两个节点,这样通过计算开始时间和结束时间之间的时间差,我们就能明确调用过程在这个 Span 上所产生的时间延迟。
通过我刚才的介绍,相信你不难理解 Trace Id 和 Span Id 之间是一对多的关系,即在一个调用链路中只会存在一个 Trace Id,但会存在多个 Span Id。这样多个 Span Id 之间就会有父子关系,即链路中的前一个 Span Id 是后一个的父 Span Id。
四种注解
现在你理解了 Trace Id 和 Span Id 的概念。但这还不够,你可以想象在整个调用链路中,势必涉及到客户端与服务端之间的各种网络交互的请求处理过程,这些过程构成了完整调用链路中的各个环节。
所以,我们就要进一步拆分整个分布式调用链路,从而细化控制的粒度。业界一般通过四种不同的注解(Annotation)记录每个服务的客户端请求和服务器响应过程。
通常包含四个注解信息: cs:Client Start,表示客户端发起请求; sr:ServerReceived,表示服务端收到请求; ss: Server Send,表示服务端完成处理,并将结果发送给客户端; cr:ClientReceived,表示客户端获取到服务端返回信息;
- cs 注解
cs 代表 Client Send,即客户端发送请求,启动整个调用链路。
- sr 注解
sr 代表 Server Received,即服务端接收请求。显然,(sr-cs)值代表请求从客户端到服务器端所需要的网络传输时间。
- ss 注解
ss 代表 Server Send,即服务端把请求处理结果返回给客户端。(ss-sr)值代表服务器端处理该请求所需要的时间。
- cr 注解
cr 代表 Client Received,即客户端接收到服务端响应,结束调用链路。(cr-ss)值代表响应结果从服务器端传输到客户端所需要的时间。
你可以结合这张示意图来进一步理解这四种注解之间的关联关系。
有了这四个注解之后,我们就可以使用它们来量化整个服务调用链路,从而找出潜在的问题。同样的,你可以看示意图辅助学习。
在这张图中,你可以看到,这次请求的 Trace Id 是 trace1,而 Span Id 根据不同的服务会发生变化,四种注解构成了客户端和服务器对一次请求处理的闭环。
对于服务 A 而言,cs 是 11:10:44,cr 是 11:10:55,也就说该次服务请求经由服务 A 的整个调用链路时间是 11s(11:10:44-11:10:55)。考虑到一半的响应时间都需要控制在 1~2s 之内,显然这个响应时间非常长。
分布式服务跟踪工具 Spring Cloud Sleuth
通过这些注解,我们就可以发现服务调用链路中存在的一系列问题,比方说前面提到的响应时间过长问题。而在日常开发过程中,我们也通常可以通过调用链路来发现,在本次请求过程中,某些服务发生的异常情况。
那么如何分析和解决这些问题呢?显然,在调用链路比较复杂的情况下,通过手动观测注解的方式不可行。这时候,我们就需要引入一些自动化工具。幸好,目前主流的服务监控实现工具都对这些注解做了支持和封装。
随着微服务架构的日益流行,Spring 自带的 Spring Cloud 应用也越来越广泛,目前已经成为这一领域的标准开发框架。所以,下面我们就基于 Spring Cloud 家族中的 Spring Cloud Sleuth(SCS)这款工具来分析具体的分布式链路跟踪使用方式。
对于分布式环境下的服务调用链路,我们可以通过 Spring Cloud Sleuth 完成两件事情:
- 服务调用链路的构建
- 服务监控数据的分析
使用 SCS 构建服务调用链路
首先,来看一看怎么实现用 SCS 构建服务调用链路。
通过将 Spring Cloud Sleuth 添加到系统的类路径,系统便会自动建立日志收集渠道。这些渠道不仅包括常见的 Spring MVC 控制器接收的 HTTP 请求,或者使用 RestTemplate 发出的请求,也能无缝支持通过 Zuul 网关发送的请求。
现在,假设我们有一个 UserService,那么通过 SCS 会生成类似这样的日志信息:
INFO [userservice,81d66b6e43e71faa,6df220755223fb6e,true] 18100 --- [nio-8082-exec-8] c.s.user.controller.UserController : Get user by userName from 8082 port of userservice instance
我们关注于上述日志信息中如下所示的四段内容,即服务名称、Trace Id、Span Id 和 Zipkin 标志位:
[服务名称, TraceId, SpanId, Zipkin标志位]
从日志信息的例子,你也能看到,
- 第一段的,UserService,代表着该服务的名称。
- 第二段中的 Trace Id,代表一次完整请求的唯一编号,例中的 81d66b6e43e71faa 就是该次请求的唯一编号。
- 而第三段中的 6df220755223fb6e,就是这个 Trace Id 下的一个 Span Id。
- 最后的第四段,代表 Zipkin 标志位,该标志位用于识别是否将服务跟踪信息同步到 Zipkin。
这里你或许会问 Zipkin 是什么,别着急,我们马上就要讲到。
到这里,服务调用链路就构建好了。那么我们如何监控数据呢?
使用 SCS 分析服务监控数据
针对监控数据的管理,你可以用 Spring Cloud Sleuth 设置常见的日志格式来输出 Trace Id 和 Span Id,也可以利用诸如 Logstash 等日志发布组件,将日志发布到 Elastic Search 等日志分析工具中进行处理。同时,Spring Cloud Sleuth 也兼容了 Zipkin、HTrace 等第三方工具的应用和集成。
在具体使用过程中,Spring Cloud Sleuth 和 Zipkin 是一个最佳组合,两者的兼容性非常好,集成过程也很简单。Zipkin 作为一款可视化工具,有其他工具所不具备的数据存储和可视化监控过程的优点。
接下来,我们就一起来看看,Zipkin 具体如何使用。
在结构上,Zipkin 包含几个核心的组件。其中,
- 收集器组件,用来接收来自各个传输器的数据
- 存储器代表存储组件,用来存储收集过来的数据
- API 组件,提供简单的 RESTful API,负责查询存储器中存储的数据
- UI 组件,提供简单的 Web 界面,可以方便而直观的查询和分析跟踪信息
对于服务监控而言,服务调用链数据收集、分析和管理的目的是,为了发现服务调用过程的问题,并采取相应的优化措施。
Zipkin 的最大优势在于提供了完整的可视化解决方案,通过它,你可以实现服务调用时序和服务调用数据的可视化。
这里我也展示了 Zipkin 可视化服务调用时序的主界面。
针对某个服务,Zipkin 的查询结果展示了包含该服务的所有调用链路,Zipkin 上的执行效果,你可以看一下。
当发起这个 HTTP 请求时,该请求会先到达网关服务 ZuulService,再通过路由转发到 UserService。图中最重要的就是各个 Span 信息。一个服务调用链路被分解成若干个 Span,每个 Span 代表完整调用链路中的一个可以衡量的部分。
通过可视化的界面,你可以看到整个访问链路的整体时长以及各个 Span 所花费的时间。每个 Span 的时延都已经被量化,并通过背景颜色的深浅来表示时延的大小。
在图中,我们点击任何一个感兴趣的 Span,就可以获取该 Span 对应的各项服务调用数据明细。
例如,我们点击“get /users/username/{username}”这个Span,Zipkin会跳转到一个新的页面并显示这样的数据。
这里,你可以看到本次调用中用于监控的最重要的元数据 Trace Id 和 Span Id。我们也可以进一步得到注解明细信息。
上图展示了针对该 Span 的 cs、sr、ss 和 cr 这四个注解数据。对于这个 Span 而言,ZuulService 相当于是 UserService 的客户端,所以 ZuulService 触发了 cs 事件,然后通过 (17.160 – 2.102)ms 到达了 UserService,以此类推。
从这些注解数据中可以得出一个结论,即该请求的整个服务响应时间主要取决于 UserService 自身的处理时间。如果这一处理时间过长,那么我们就可以有针对性的采取优化措施。
业内常用链路追踪系统比较
以下是常用链路追踪系统在 OpenTracing 兼容性、支持的客户端语言、储存方式、实现的传输协议、代码侵入性、扩展性、告警等方面的对比表格:
链路追踪系统 | OpenTracing 兼容性 | 支持的客户端语言 | 储存方式 | 实现的传输协议 | 代码侵入性 | 扩展性 | 告警功能 |
---|---|---|---|---|---|---|---|
Jaeger | 兼容,基于 OpenTracing 标准构建,后遵循 OpenTelemetry 标准 | Go、Java、Python、C++、JavaScript 等多种主流语言 | Cassandra、Elasticsearch、Kafka 等 | HTTP、gRPC | 低,通过 SDK 可实现无侵入式埋点 | 高,支持多存储后端,可水平扩展 | 可与 Prometheus 等监控系统集成实现告警 |
Zipkin | 兼容 OpenTracing,也支持 OpenTelemetry | Java、Python、Go、Ruby、JavaScript 等 | MySQL、Elasticsearch、Cassandra 等 | HTTP、Thrift、Kafka | 低,有多种语言的客户端库,可无侵入式接入 | 较高,可通过扩展存储和传输组件实现 | 可结合第三方监控系统实现 |
SkyWalking | 支持 OpenTracing,全面支持 OpenTelemetry | Java、.NET Core、Node.js、Python、Go 等 | Elasticsearch、H2、MySQL、TiDB 等 | gRPC、HTTP | 低,Java 有自动探针,其他语言也有轻量级 SDK | 高,支持多种插件扩展和集群部署 | 内置告警规则,可灵活配置 |
Pinpoint | 不直接兼容 OpenTracing,但有一定的开放性可集成 | 主要为 Java,有少量其他语言支持 | HBase | TCP | 低(Java 通过字节码注入) | 较高,支持分布式部署和插件扩展 | 有内置告警功能 |
DataDog | 支持 OpenTracing 和 OpenTelemetry | 多种主流语言如 Python、Java、Go、Ruby 等 | 自有分布式存储 | HTTP | 低,有各语言 APM 库 | 高,云原生架构易于扩展 | 丰富的告警规则设置和通知渠道 |
New Relic | 支持 OpenTracing | Java、.NET、Python、Node.js、Go 等 | 自有存储系统 | HTTP | 低,有各语言代理 | 高,可按需扩展 | 多种告警策略和通知方式 |
总结
可以说,构建服务监控和链路跟踪,在分布式系统开发过程中,是一项基础设施类工作。关于服务监控的基本原理其实并不复杂,业界也已经形成了一套比较统一的专业术语和方法论。而 Spring Cloud Sleuth 就是基于这些原理的一种具体实现工具。
Spring Cloud Sleuth 为我们提供了一种代码免侵入的整合方案,在多个服务进行交互的过程中,通过获取 Trace 和 Span 信息就能构建整个服务调用链路。同时,它还可以通过集成 Zipkin,提供可视化服务、调用时序、获取服务调用数据等强大功能。