在分布式系统中,我们基于业务划分服务,并对外暴露服务访问接口。在中大型系统中,可能需要很多个服务相互协同,才能完成一个接口功能。而随着业务的不断扩张,服务之间相互调用关系会越来越复杂。
从这张分布式服务调用链路的示意图中,你也可以看到,随着服务数量的不断增加,整个调用链路的分析工作变得越来越复杂。
显然,通过人工手段已经无法完成这种服务调用链路的分析。这时候,我们就需要引入分布式服务跟踪机制,并借助于一定的工具,来实现微服务架构下的服务监控。
首先,我们先来分析一下分布式服务跟踪机制的基本原理,这里,你需要先理解两个基本概念: Trace Id 和 Span Id。这两个概念是对服务跟踪最高层次的抽象,也是掌握分布式服务跟踪原理的基础。
Trace Id 即跟踪 ID。在分布式架构中,每个请求会生成一个全局的唯一性 ID,通过这个 ID 可以串联起整个调用链,也就是说请求在分布式系统内部流转时,系统需要始终保持传递该唯一性 ID,直到请求返回。这个唯一性 ID 就是 Trace 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 Send,即客户端发送请求,启动整个调用链路。
sr 代表 Server Received,即服务端接收请求。显然,(sr-cs)值代表请求从客户端到服务器端所需要的网络传输时间。
ss 代表 Server Send,即服务端把请求处理结果返回给客户端。(ss-sr)值代表服务器端处理该请求所需要的时间。
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 自带的 Spring Cloud 应用也越来越广泛,目前已经成为这一领域的标准开发框架。所以,下面我们就基于 Spring Cloud 家族中的 Spring Cloud Sleuth(SCS)这款工具来分析具体的分布式链路跟踪使用方式。
对于分布式环境下的服务调用链路,我们可以通过 Spring Cloud Sleuth 完成两件事情:
首先,来看一看怎么实现用 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标志位]
从日志信息的例子,你也能看到,
这里你或许会问 Zipkin 是什么,别着急,我们马上就要讲到。
到这里,服务调用链路就构建好了。那么我们如何监控数据呢?
针对监控数据的管理,你可以用 Spring Cloud Sleuth 设置常见的日志格式来输出 Trace Id 和 Span Id,也可以利用诸如 Logstash 等日志发布组件,将日志发布到 Elastic Search 等日志分析工具中进行处理。同时,Spring Cloud Sleuth 也兼容了 Zipkin、HTrace 等第三方工具的应用和集成。
在具体使用过程中,Spring Cloud Sleuth 和 Zipkin 是一个最佳组合,两者的兼容性非常好,集成过程也很简单。Zipkin 作为一款可视化工具,有其他工具所不具备的数据存储和可视化监控过程的优点。
接下来,我们就一起来看看,Zipkin 具体如何使用。
在结构上,Zipkin 包含几个核心的组件。其中,
对于服务监控而言,服务调用链数据收集、分析和管理的目的是,为了发现服务调用过程的问题,并采取相应的优化措施。
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 自身的处理时间。如果这一处理时间过长,那么我们就可以有针对性的采取优化措施。
可以说,构建服务监控和链路跟踪,在分布式系统开发过程中,是一项基础设施类工作。关于服务监控的基本原理其实并不复杂,业界也已经形成了一套比较统一的专业术语和方法论。而 Spring Cloud Sleuth 就是基于这些原理的一种具体实现工具。
Spring Cloud Sleuth 为我们提供了一种代码免侵入的整合方案,在多个服务进行交互的过程中,通过获取 Trace 和 Span 信息就能构建整个服务调用链路。同时,它还可以通过集成 Zipkin,提供可视化服务、调用时序、获取服务调用数据等强大功能。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。