Board logo

标题: 分布式集群环境下调用链路追踪(2)基于 Google Dapper 的分布式追踪介绍 [打印本页]

作者: look_w    时间: 2018-11-29 17:50     标题: 分布式集群环境下调用链路追踪(2)基于 Google Dapper 的分布式追踪介绍

基于 Google Dapper 的分布式追踪介绍当我们进行微服务架构开发时,通常会根据业务或功能划分成若干个不同的微服务模块,各模块之间通过 REST/RPC        等协议进行调用。一个用户在前端发起某种操作,可能需要很多微服务的协同才能完成,如果在业务调用链路上任何一个微服务出现问题或者网络超时,将导致功能失效,或出现某些模块在调用链中耗时突然增大等问题。随着业务越来越复杂,微服务架构横向、纵向扩展之后,其规模越来越大,对于微服务之间的调用链路的分析将会越来越复杂。此时你会发现,如果在最初架构设计时,能够将分布式链路追踪这种需求考虑进来,后期微服务集群扩容时候,前期所做的工作将会达到事半功倍的效果。
下面先来看看 Google Dapper 论文里面介绍的一个非常简单、常见的一个场景:
图 1. 服务调用示例引用自文章 " " 中的 Figure        1
图 1 中 A~E 分别表示五个服务,用户发起一次 X 请求到前端系统 A,然后 A 分别发送 RPC 请求到中间层 B 和 C,B 处理请求后返回,C 还要发起两个 RPC        请求到后端系统 D 和 E。
以上完整调用回路中,一次请求需要经过多个系统处理完成,并且追踪系统像是内嵌在 RPC 调用链上的树形结构,然而,我们的核心数据模型不只局限于特定的 RPC        框架,我们还能跟踪其他行为,例如外界的 HTTP 请求,和外部对 Kafka 服务器的调用等。从形式上看,分布式追踪模型使用的 Trace        树形结构来记录请求之间的关系(父子关系、先后顺序等)。
Trace 和        SpanGoogle Dapper 中关于 Trace 的介绍中主要有以下三个主要元素构成:
分布式追踪系统要做的就是记录每次发送和接受动作的标识符和时间戳,将一次请求涉及到的所有服务串联起来,只有这样才能清楚记录每次请求的完整调用链。在分布式追踪系统中使用 Trace        表示对一次请求完整调用链的跟踪,将两个服务例如上面图 1 中的服务 A 和服务 B 的请求/响应过程叫做一次 Span。 我们可以看出每一次跟踪 Trace 都是一个树型结构,Span        可以体现出服务之间的具体依赖关系。
对于每个 Trace 树,Trace 都要定义一个全局唯一的 Trace ID,在这个跟踪中的所有 Span 都将获取到这个 Trace ID。 每个 Span 都有一个        Parent Span ID 和它自己的 Span ID。上面图 1 中 A 服务的 Parent Span ID 为空,Span ID 为 1;然后 B 服务的 Parent        Span ID 为 1,Span ID 为 2;C 服务的 Parent Span ID 也为 1,Span ID 为 3,依次类推,如图 2 所示:
图 2. Trace 树
引用自文章 " " 中的 Figure        2
追踪系统中用 Span 来表示一个服务调用的开始和结束时间,也就是时间区间。追踪系统记录了 Span 的名称以及每个 Span ID 的 Parent Span ID,如果一个        Span 没有 Parent Span ID 则被称为 Root Span,当前节点的 Parent Span ID 即为调用链路上游的 Span ID,所有的 Span        都挂在一个特定的追踪上,共用一个 Trace ID。
Span        内部结构下面看一下 Span 的内部结构,Span 除了记录 Parent Span ID 和自身的 Span ID        外,还会记录自己请求其他服务的时间和响应时间。但是每个服务器的时间可能不是完全相同,为了解决这个问题需要约定一个前提,即 RPC        客户端必须发出请求后,服务端才能收到,如果服务端的时间戳比客户端发出请求的时间戳还靠前,那么就按请求时间来算,响应时间也是如此。
任何一个 Span 可以包含来自不同 Server 的信息,这些也要记录下来。事实上,每一个 RPC Span 可能包含客户端和服务器两个过程的 Annotation        内容。由于客户端和服务器上的时间戳来自不同的主机,我们必须考虑到 Server        之间的时间偏差。在链路追踪分析中,每一个请求,总是一方先发送一个请求之后,然后另外一方才接收到;这样一来,每个调用请求就有一个时间戳的上限和下限。
图 3. Span 的细节图引用自文章 " " 中的 Figure        3
从图 3 可以可以很清楚的看出,这是一次 Span 名为 Hello.Call 的调用,Span ID 是 5,Parent Span ID 是 3,Trace ID 是        100。 我们重点看一下 Span 对应的四个状态:
通过收集这四个时间戳,就可以在一次请求完成后计算出整个 Trace 的执行耗时和网络耗时,以及 Trace 中每个 Span 过程的执行耗时和网络耗时:
生成        Span我们已经初步了解了 Span 的组成,那么怎么生成 Span 呢?Google Dapper 中使用到的是基于标注 (Annotation-based)        的监控方案。此方案会有代码侵入,所以应尽可能少改动代码。
基于标注的方式就是根据请求中的 Trace ID 来获取 Trace 这个实例,各种编程语言有各自的方式。获取到 Trace 实例后就可以调用 Recorder 来记录 Span        了,记录值先直接以日志的形式存在本地,然后跟踪系统会启动一个 Collector Daemon 来收集日志,然后整理日志写入数据库。解析的日志结果建议放在 BigTable        (Cassandra 或者 HDFS) 这类稀疏表的数据库里。因为每个 Trace 携带的 Span 可能不一样,最终的记录是每一行代表一个 Trace,这一行的每一列代表一个        Span,如图 4 所示:
图 4. Trace 数据存储引用自文章 " " 中的 Figure        5
对于减少代码的侵入性,论文建议将核心跟踪代码做的很轻巧,然后把它植入公共组件中,比如线程调用、控制流以及 RPC 库。
采样率分布式跟踪系统的实现要求性能低损耗,尤其在生产环境中不能影响到核心业务的性能,也不可能每次请求都跟踪,所以要进行采样,每个应用和服务可以自己设置采样率。采样率应该是在每个应用自己的配置里设置的,这样每个应用可以动态调整,特别是刚应用刚上线时可以适当调高采样率。
一般在系统峰值流量很大的情况下,只需要采样其中很小一部分请求,例如 1/1000 的采样率,即分布式跟踪系统只会在 1000 次请求中采样其中的某一次。
Trace        收集分布式跟踪系统的追踪记录和收集管道的过程分为三个阶段:
Bigtable 的支持稀疏表格布局正适合这种情况,因为每一次追踪请求可能产生多个 Span。写入数据后的 Bigtable,每一条记录集表示一条 Trace        记录。这些监控数据的汇总是单独进行的,而不是伴随系统对用户的应答一起返回的。如此选择主要有如下的两个原因:
基于这两个考虑,最终选择将监控数据和应答信息分开传输。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0