Google Dapper 论文笔记

2017-03-14


Google Dapper 论文笔记

作用:

追踪分布式系统,理解系统行为,分析性能问题。监控横跨不同应用,服务器之间的关联动作。
尽可能快的获取反馈,存入后一分钟内就能统计出来,这样就可以对生产环境下的异常状况做出快速反应。侧重性能方面的调查。

要求:

低损耗,应用透明,大范围部署,持续监控,无所不在。采样率和公共库就是为了满足前面的要求。

  1. 低消耗:跟踪系统对在线服务的影响应该做到足够小。在一些高度优化过的服务,即使一点点损耗也会很容易察觉到,而且有可能迫使在线服务的部署团队不得不将跟踪系统关停。

  2. 应用级的透明:对于应用的程序员来说,是不需要知道有跟踪系统这回事的。
    如果一个跟踪系统想生效,就必须需要依赖应用的开发者主动配合,那么这个跟踪系统也太脆弱了,往往由于跟踪系统在应用中植入代码的bug或疏忽导致应用出问题,这样是无法满足对跟踪系统“无所不在的部署”这个需求的。面对当下想Google这样的快节奏的开发环境来说,尤其重要。

  3. 延展性:Google至少在未来几年的服务和集群的规模,监控系统都应该能完全把控住。
    我们把核心跟踪代码做的很轻巧,然后把它植入到那些无所不在的公共组件中,比如线程调用、控制流以及RPC库。使用自适应的采样率可以使跟踪系统变得可伸缩,并降低性能损耗,

例子:

搜索服务,对结果耗时敏感,因此我们想要定位出哪个环节耗时最多。 img
图1:这个路径由用户的X请求发起,穿过一个简单的服务系统。用字母标识的节点代表分布式系统中的不同处理过程。

img
图2:5个span在Dapper跟踪树中短暂的关联关系

在图2中说明了span在一个大的跟踪过程中是什么样的。Dapper记录了span名称,以及每个span的ID和父ID,以重建在一次追踪过程中不同span之间的关系。如果一个span没有父ID被称为root span。所有span都挂在一个特定的跟踪上,也共用一个跟踪id(在图中未示出)。所有这些ID用全局唯一的64位整数标示。在一个典型的Dapper跟踪中,我们希望为每一个RPC对应到一个单一的span上,而且每一个额外的组件层都对应一个跟踪树型结构的层级。 img
图3:在图3中所示的一个单独的span的细节图

记住,任何一个span可以包含来自不同的主机信息,这些也要记录下来。事实上,每一个RPC span可以包含客户端和服务器两个过程的注释,使得链接两个主机的span会成为模型中所说的span。

植入点:

  • 当一个线程在处理跟踪控制路径的过程中,Dapper把这次跟踪的上下文在ThreadLocal中进行存储。追踪上下文是一个小而且容易复制的容器,其中承载了span的属性比如trace ID和span ID。
  • 当计算过程是延迟调用的或是异步的,大多数Google开发者通过线程池或其他执行器,使用一个通用的控制流库来回调。Dapper确保所有这样的回调可以存储这次跟踪的上下文,而当回调函数被触发时,这次跟踪的上下文会与适当的线程关联上。在这种方式下,Dapper可以使用trace ID和span ID来辅助构建异步调用的路径。
  • 几乎所有的Google的进程间通信是建立在一个用C++和Java开发的RPC框架上。我们把跟踪植入该框架来定义RPC中所有的span。span ID和trace ID会从客户端发送到服务端。像那样的基于RPC的系统被广泛使用在Google中,这是一个重要的植入点。当那些非RPC通信框架发展成熟并找到了自己的用户群之后,我们会计划对RPC通信框架进行植入。

标记 Annotation

除了简单的文本Annotation,Dapper也支持的key-value映射的 Annotation,提供给开发人员更强的跟踪能力,如持续的计数器,二进制消息记录和在一个进程上跑着的任意的用户数据。键值对的Annotation方式用来在分布式追踪的上下文中定义某个特定应用程序的相关类型。

采样率:

因此,除了把Dapper的收集工作对基本组件的性能损耗限制的尽可能小之外,我们还有进一步控制损耗的办法,那就是遇到大量请求时只记录其中的一小部分。 img 图5:Dapper收集管道的总览

跟踪收集

Dapper的跟踪记录和收集管道的过程分为三个阶段(参见图5)。首先,span数据写入(1)本地日志文件中。然后Dapper的守护进程和收集组件把这些数据从生产环境的主机中拉出来(2),最终写到(3)Dapper的Bigtable仓库中。一次跟踪被设计成Bigtable中的一行,每一列相当于一个span。Bigtable的支持稀疏表格布局正适合这种情况,因为每一次跟踪可以有任意多个span。

带内带外数据搜集

带内就是和RPC请求响应的header带信息。 带外就是写日志,然后通过另外的工具收集日志而不走RPC。 其次,带内收集方案假定所有的RPC是完美嵌套的。我们发现,在所有的后端的系统返回的最终结果之前,有许多中间件会把结果返回给他们的调用者。带内收集系统是无法解释这种非嵌套的分布式执行模式的。

Dapper代码中中最关键的部分,就是对基础RPC、线程控制和流程控制的组件库的植入,其中包括span的创建,采样率的设置,以及把日志写入本地磁盘。

Dapper提供了一个简单的库来帮助开发者手动控制跟踪传播作为一种变通方法

性能消耗:

跟踪系统的成本由两部分组成:

  1. 正在被监控的系统在生成追踪和收集追踪数据的消耗导致系统性能下降,

  2. 需要使用一部分资源来存储和分析跟踪数据。

生成跟踪的开销是Dapper性能影响中最关键的部分,因为收集和分析可以更容易在紧急情况下被关闭。Dapper运行库中最重要的跟踪生成消耗在于创建和销毁span和annotation,并记录到本地磁盘供后续的收集
根span的创建和销毁需要损耗平均204纳秒的时间,而同样的操作在其他span上需要消耗176纳秒。时间上的差别主要在于需要在跟span上给这次跟踪分配一个全局唯一的ID。

在Dapper运行期写入到本地磁盘是最昂贵的操作,但是他们的可见损耗大大减少,因为写入日志文件和操作相对于被跟踪的应用系统来说都是异步的。不过,日志写入的操作如果在大流量的情况,尤其是每一个请求都被跟踪的情况下就会变得可以察觉到。

低流量低负载自动提高采样率,而在高流量高负载的情况下会降低采样率,使损耗一直保持在控制之下。

索引

选择一个合适的自定义索引是Dapper设计中最具挑战性的部分。压缩存储要求在跟踪数据种建立一个索引的情况只比实际数据小26%,所以消耗是巨大的。
最初,我们部署了两个索引:第一个是主机索引,另一个是服务名的索引。然而,我们并没有找到主机索引和存储成本之间的利害关系。
当用户对每一台主机感兴趣的时候,他们也会对特定的服务感兴趣,所以我们最终选择把两者相结合,成为一个组合索引,它允许以服务名称,主机,和时间戳的顺序进行有效的查找。

参考资料和扩展阅读

原文Paper:Dapper, a Large-Scale Distributed Systems Tracing Infrastructure
中文翻译:Dapper,大规模分布式系统的跟踪系统