《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》 论文阅读
《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》 论文阅读
参考链接:https://bigbully.github.io/Dapper-translation/
概述
诞生背景
- 当代的互联网的服务,通常都是用复杂的、大规模分布式集群来实现的。
- 依赖不同的软件模块,可能由不同的团队开发、可能使用不同的编程语言、有可能布在了几千台服务器,横跨多个不同的数据中心
设计目标
- 低损耗
- 应用透明的
- 大范围部署
技术要点
- 采样率的使用
- 把代码植入限制在一小部分公共库的改造上
介绍
技术难点
- 目的是为了收集更多的复杂分布式系统的行为信息
- 为了在这个上下文中理解分布式系统的行为,就要监控那些横跨了不同的应用、不同的服务器之间的关联动作
场景举例
- 一个前段服务可能对上百台查询服务器发起了一个Web查询,每一个查询都有自己的Index。
- 这个查询可能会被发送到多个的子系统,这些子系统分别用来处理广告、进行拼写检查或是查找一些像图片、视频或新闻这样的特殊结果。
- 根据每个子系统的查询结果进行筛选,得到最终结果,最后汇总到页面上。
- 我们把这种搜索模型称为“全局搜索”(universal search)。
- 这一次全局搜索有可能调用上千台服务器,涉及各种服务。
- 用户对搜索的耗时是很敏感的,而任何一个子系统的低效都导致导致最终的搜索耗时。
- 我们只能知道这个查询耗时不正常,但是不知道到底是由哪个服务调用造成的,或者为什么调用性能不好
难点汇总
- 无法准确的定位到这次全局搜索是调用了哪些服务
- 因为新的服务、乃至服务上的某个片段,都有可能在任何时间上过线或修改过
- 有可能是面向用户功能,也有可能是一些例如针对性能或安全认证方面的功能改进。
- 对所有参与这次全局搜索的服务不会都了如指掌
- 每一个服务都有可能是由不同的团队开发或维护的。
- 这些暴露出来的服务或服务器有可能同时还被其他客户端使用着
- 所以这次全局搜索的性能问题甚至有可能是由其他应用造成的。
- 举个例子,一个后台服务可能要应付各种各样的请求类型,而一个使用效率很高的存储系统,比如Bigtable,有可能正被反复读写着,因为上面跑着各种各样的应用。
需求和设计目标
- 无所不在的部署
- 即便只有一小部分没被监控到,对这个系统是不是值得信任都会产生巨大的质疑。
- 持续的监控另外
- 系统异常或是那些重要的系统行为有可能出现过一次,就很难甚至不太可能重现
设计目标
-
低消耗
- 对在线服务的影响应该做到足够小
- 在一些高度优化过的服务,即使一点点损耗也会很容易察觉到,有可能不得不将跟踪系统关停。
-
应用级的透明
- 对于应用的程序员来说,是不需要知道有跟踪系统这回事的,否则这个跟踪系统也太脆弱了
- 是当下面临的最挑战性的设计目标,通过把核心跟踪代码做的很轻巧,然后把它植入到那些无所不在的公共组件种,比如线程调用、控制流以及RPC库
-
延展性
- 至少在未来几年的服务和集群的规模,监控系统都应该能完全把控住。
-
跟踪数据产生之后,进行分析的速度要快
- 理想情况是数据存入跟踪仓库后一分钟内就能统计出来
- 提供足够快的信息反馈,就可以对生产环境下的异常状况做出快速反应
架构介绍
模型选择
请求模型
- 上图一个和5台服务器相关的一个服务,包括:前端(A),两个中间层(B和C),以及两个后端(D和E)。
- 当一个用户(这个用例的发起人)发起一个请求时,首先到达前端,然后发送两个RPC到服务器B和C。
- B会马上做出反应,但是C需要和后端的D和E交互之后再返还给A,由A来响应最初的请求。
模型目的
- 为服务器上每一次发送和接收动作来收集跟踪标识符(message identifiers)和时间戳(timestamped events)。
- 同时将所有记录条目与一个给定的发起者(例如,图1中的RequestX)关联上
模型方案
-
现在有两种解决方案,黑盒(black-box)和基于标注(annotation-based)的监控方案
- 黑盒方案需要跟踪的除了上述信息之外没有额外的信息,使用统计回归技术来推断两者之间的关系
- 基于标注的方案依赖于应用程序或中间件明确地标记一个全局ID,从而连接每一条记录和发起者的请求
-
黑盒方案比标注方案更轻便,但是他们需要更多的数据,以获得足够的精度,因为他们依赖于统计推论。
-
基于标注的方案最主要的缺点是需要代码植入
- 因为所有的应用程序都使用相同的线程模型,控制流和RPC系统,
- 所以可以把代码植入限制在一个很小的通用组件库中,从而实现监测系统的应用对开发人员的透明
-
Dapper的跟踪架构像是内嵌在RPC调用的树形结构。也可以跟踪其他行为,例如Gmail的SMTP会话,外界的HTTP请求,和外部对SQL服务器的查询等。Dapper跟踪模型使用的树形结构,Span以及Annotation
跟踪树和span
- 树节点是整个架构的基本单元,而每一个节点又是对span的引用。
- 节点之间的连线表示的span和它的父span直接的关系。
- 虽然span在日志文件中只是简单的代表span的开始和结束时间,他们在整个树形结构中却是相对独立的
- Dapper记录了span名称,以及每个span的ID和父ID,以重建在一次追踪过程中不同span之间的关系。
- 如果一个span没有父ID被称为root span。
- 所有span都挂在一个特定的跟踪上,也共用一个跟踪id(在图中未示出)。
- 所有这些ID用全局唯一的64位整数标示。
- 在一个典型的Dapper跟踪中,我们希望为每一个RPC对应到一个单一的span上,而且每一个额外的组件层都对应一个跟踪树型结构的层级
- 这种某个span表述了两个“Helper.Call”的RPC(分别为server端和client端)。span的开始时间和结束时间,以及任何RPC的时间信息都通过Dapper在RPC组件库的植入记录下来。
- 应用程序开发者也可以在跟踪中增加他们自己的注释(如图中“foo”的注释)
- 任何一个span可以包含来自不同的主机信息,这些也要记录下来。
- 每一个RPC span可以包含客户端和服务器两个过程的注释,使得链接两个主机的span会成为模型中所说的span
- 由于客户端和服务器上的时间戳来自不同的主机,需要考虑到时间偏差:RPC客户端发送一个请求之后,服务器端才能接收到,对于响应也是一样的(服务器先响应,然后客户端才能接收到这个响应)。这样一来,服务器端的RPC就有一个时间戳的一个上限和下限。
植入点
-
几乎完全依赖于基于少量通用组件库的改造
- 线程内,Dapper把这次跟踪的上下文信息在ThreadLocal中进行存储。追踪上下文是一个小而且容易复制的容器,其中包含Scan的属性比如跟踪ID和span ID。
- 异步处理,通过线程池或其他执行器,使用一个通用的控制流库来回调。Dapper确保所有这样的回调可以存储这次跟踪的上下文,而当回调函数被触发时,这次跟踪的上下文会与适当的线程关联上。在这种方式下,Dapper可以使用trace ID和span ID来辅助构建异步调用的路径。
- RPC框架,进程间通信是建立在一个C++和Java开发的RPC框架上,我们把跟踪植入该框架来定义RPC中所有的span。span的ID和跟踪的ID会从客户端发送到服务端。对于非RPC通信框架发展成熟并找到了自己的用户群之后,会对其进行植入。
-
Dapper的跟踪数据是独立于语言的,很多在生产环境中的跟踪结合了用C++和Java写的进程的数据
Annotation
-
Dapper还允许应用程序开发人员添加额外的信息,以监控更高级别的系统行为,或帮助调试问题。
-
通过一个简单的API定义带时间戳的Annotation,这些Annotation可以添加任意内容。
-
为了防止过多的信息上报,每一个跟踪span有一个可配置的总Annotation量的上限。
-
应用程序级的Annotation是不能替代用于表示span结构的信息和记录着RPC相关的信息。
-
除了简单的文本Annotation,Dapper也支持的key-value映射的 Annotation,提供给开发人员更强的跟踪能力,如持续的计数器,二进制消息记录和在一个进程上跑着的任意的用户数据。键值对的Annotation方式用来在分布式追踪的上下文中定义某个特定应用程序的相关类型。
采样率
- 低损耗的是Dapper的一个关键的设计目标
- 让开发人员使用Annotation的API,而不用担心额外的开销
- 某些类型的Web服务对植入带来的性能损耗确实非常敏感
- 因此,除了把Dapper的收集工作对基本组件的性能损耗限制的尽可能小之外,我们还有采样率方案
跟踪的收集
- 首先,span数据写入(1)本地日志文件中
- 然后Dapper的守护进程和收集组件把这些数据从生产环境的主机中拉出来(2)
- 最终写到(3)Dapper的Bigtable仓库中
关于格式
- 一次跟踪被设计成Bigtable中的一行,每一列相当于一个span。
- Bigtable的支持稀疏表格布局正适合这种情况,因为每一次跟踪可以有任意多个span
关于延迟
- 跟踪数据收集的延迟中位数少于15秒。
- p98往往随着时间的推移呈现双峰型
- 大约75%的时间,p98小于2分钟,但是另外大约25%的时间,它可以增涨到几个小时。
带外数据跟踪收集
什么是带外数据
-
传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方。为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道。
-
Dapper系统请求树树自身进行跟踪记录和收集带外数据。
-
首先,带内收集方案--这里跟踪数据会以RPC响应头的形式被返回--会影响应用程序网络动态。
- 在Google里的许多规模较大的系统中,一次跟踪成千上万的span并不少见。
- 然而,RPC回应大小仍然是比较小的:通常小于10K。
- 在这种情况下,带内Dapper的跟踪数据会让应用程序数据和倾向于使用后续分析结果的数据量相形见绌。
-
其次,带内收集方案假定所有的RPC是完美嵌套的。
- 在所有的后端的系统返回的最终结果之前,有许多中间件会把结果返回给他们的调用者。
- 带内收集系统是无法解释这种非嵌套的分布式执行模式的。
安全和隐私考虑
-
记录一定量的RPC有效负载信息将丰富Dapper的跟踪能力,
-
然而,有效载荷数据可能包含的一些不应该透露给未经授权用户(包括正在debug的工程师)的内部信息。
-
由于安全和隐私问题是不可忽略的,虽然存储RPC方法的名称,但在这个时候不记录任何有效载荷数据
-
相反,应用程序级别的Annotation提供了一个方便的可选机制:应用程序开发人员可以在span中选择关联那些为以后分析提供价值的数据。
-
Dapper还提供了一些安全上的便利,是它的设计者事先没有预料到的。
- 通过跟踪公开的安全协议参数,Dapper可以通过相应级别的认证或加密,监视应用程序是否满足安全策略
- 例如。Dapper还可以提供信息,以基于策略的的隔离系统按预期执行,例如支撑敏感数据的应用程序不与未经授权的系统组件进行了交互。这样的测算提供了比源码审核更强大的保障。
Dapper部署状况
Dapper运行库
- 除了做到轻量级,植入的代码更需要稳定和健壮
- 植入的核心代码是由未超过1000行的C++和不超过800行Java代码组成。
- 为了支持键值对的Annotation还添加了额外的500行代码。
生产环境下的涵盖面
部署情况
-
创建Dapper跟踪的过程和Dapper跟踪收集守护进程
- Dapper的守护进程存在于Google几乎所有的服务器上。
- 考虑到无处不在Dapper组件的植入库,我们认为每一个Google的生产进程都是支持跟踪的。
-
在某些情况下Dapper的是不能正确的跟踪控制路径的。
- 这些通常源于使用非标准的控制流,或是Dapper的错误的把路径关联归到不相关的事件上。
- Dapper提供了一个简单的库来帮助开发者手动控制跟踪传播作为一种变通方法。
- 目前有40个C++应用程序和33个Java应用程序需要一些手动控制的追踪传播,不过这只是上千个的跟踪中的一小部分。
- 也有非常小的一部分程序使用的非组件性质的通信库(比如原生的TCP Socket或SOAP RPC),因此不能直接支持Dapper的跟踪
-
考虑到生产环境的安全,Dapper的跟踪也可以关闭。
- 事实上,它在部署的早起就是默认关闭的,直到Dapper的稳定性和低损耗得到了验证才开启。
跟踪Annotation的使用
-
程序员倾向于使用特定应用程序的Annotation,无论是作为一种分布式调试日志文件,还是通过一些应用程序特定的功能对跟踪进行分类。例如,所有的Bigtable的请求会把被访问的表名也记录到Annotation中。目前,70%的Dapper span和90%的所有Dapper跟踪都至少有一个特殊应用的Annotation。
-
41个Java应用和68个C++应用中都添加自定义的Annotation为了更好地理解应用程序中的span在他们的服务中的行为。值得注意的是,迄今为止我们的Java开发者比C++开发者更多的在每一个跟踪span上采用Annotation的API。这可能是因为我们的Java应用的作用域往往是更接近最终用户(C++偏底层);这些类型的应用程序经常处理更广泛的请求组合,因此具有比较复杂的控制路径。
处理跟踪损耗
- 跟踪系统的性能损失由两部分组成
- 被监控的系统在生成追踪和收集追踪数据
- 需要使用一部分资源来存储和分析跟踪数据
生成跟踪的损耗
-
生成跟踪的开销是Dapper性能影响中最关键的部分,因为收集和分析可以更容易在紧急情况下被关闭
-
Dapper运行库中最重要的跟踪生成消耗在于创建和销毁span和annotation,并记录到本地磁盘供后续的收集。
-
根span的创建和销毁需要损耗平均204纳秒的时间,而同样的操作在其他span上需要消耗176纳秒。
-
时间上的差别主要在于需要在跟span上给这次跟踪分配一个全局唯一的ID。
-
如果一个span没有被采样的话,那么这个额外的span下创建annotation的成本几乎可以忽略不计,他由在Dapper运行期对ThreadLocal查找操作构成,这平均只消耗9纳秒。
-
如果这个span被计入采样的话,会用一个用字符串进行标注平均需要消耗40纳秒。
-
这些数据都是在2.2GHz的x86服务器上采集的。
-
在Dapper运行期写入到本地磁盘是最昂贵的操作,写入日志文件和操作都是异步的。
-
不过,日志写入的操作如果在大流量的情况,尤其是每一个请求都被跟踪的情况下就会变得可以察觉到
跟踪收集的消耗
-
读出跟踪数据也会对正在被监控的负载产生干扰。
-
表1展示的是最坏情况下,Dapper收集日志的守护进程在高于实际情况的负载基准下进行测试时的cpu使用率。
-
在生产环境下,跟踪数据处理中,这个守护进程从来没有超过0.3%的单核cpu使用率,而且只有很少量的内存使用以及堆碎片的噪音。
-
限制了Dapper守护进程为内核scheduler最低的优先级,以防在一台高负载的服务器上发生cpu竞争。
-
Dapper也是一个带宽资源的轻量级的消费者,每一个span在仓库中传输只占用了平均426的byte。
-
作为网络行为中的极小部分,Dapper的数据收集在Google的生产环境中的只占用了0.01%的网络资源。
在生产环境下对负载的影响
-
每个请求都会利用到大量的服务器的高吞吐量的线上服务,这是对有效跟踪最主要的需求之一;这种情况需要生成大量的跟踪数据,并且他们对性能的影响是最敏感的。
-
在表2中我们用集群下的网络搜索服务作为例子,我们通过调整采样率,来衡量Dapper在延迟和吞吐量方面对性能的影响。
-
虽然对吞吐量的影响不是很明显,但为了避免明显的延迟,跟踪的采样还是必要的。
-
然而,延迟和吞吐量的带来的损失在把采样率调整到小于1/16之后就全部在实验误差范围内。
-
在实践中,我们发现即便采样率调整到1/1024仍然是有足够量的跟踪数据的用来跟踪大量的服务。
-
保持Dapper的性能损耗基线在一个非常低的水平是很重要的,因为它为那些应用提供了一个宽松的环境使用完整的Annotation API而无惧性能损失。
-
使用较低的采样率还有额外的好处,可以让持久化到硬盘中的跟踪数据在垃圾回收机制处理之前保留更长的时间,这样为Dapper的收集组件给了更多的灵活性。
可变采样
-
任何给定进程的Dapper的消耗和每个进程单位时间的跟踪的采样率成正比。
-
较低的采样率和较低的传输负载下可能会导致错过重要事件,较高的采样率就需要能接受的性能损耗。
-
解决方案就是覆盖默认的采样率,这需要手动干预的,这种情况是我们试图避免在dapper中出现的。
-
我们在部署可变采样的过程中,参数化配置采样率时,不是使用一个统一的采样方案,而是使用一个采样期望率来标识单位时间内采样的追踪。
-
低流量低负载自动提高采样率,而在高流量高负载的情况下会降低采样率,使损耗一直保持在控制之下。
-
实际使用的采样率会随着跟踪本身记录下来,这有利于从Dapper的跟踪数据中准确的分析。
Coping with aggressive sampling
- 新的Dapper用户往往觉得低采样率--在高吞吐量的服务下经常低至0.01%--将会不利于他们的分析。
- 低吞吐量的服务--也许是每秒请求几十次,而不是几十万--可以负担得起跟踪每一个请求,这是促使我们下决心使用自适应采样率的原因。
在收集过程中额外的采样
-
上述采样机制被设计为尽量减少与Dapper运行库协作的应用程序中明显的性能损耗。
-
Dapper的团队还需要控制写入中央资料库的数据的总规模,因此为达到这个目的,我们结合了二级采样。
-
目前我们的生产集群每天产生超过1TB的采样跟踪数据。
-
Dapper的用户希望生产环境下的进程的跟踪数据从被记录之后能保存至少两周的时间。
-
逐渐增长的追踪数据的密度必须和Dapper中央仓库所消耗的服务器及硬盘存储进行权衡。
-
对请求的高采样率还使得Dapper收集器接近写入吞吐量的上限。
-
为了维持物质资源的需求和渐增的Bigtable的吞吐之间的灵活性,在收集系统自身上增加额外的采样率的支持
-
对于在收集系统中的每一个span,我们用hash算法把跟踪ID转成一个标量Z,这里0<=Z<=1。如果Z比我们收集系统中的系数低的话,我们就保留这个span信息,并写入到Bigtable中。反之,我们就抛弃他。通过在采样决策中的跟踪ID,我们要么保存、要么抛弃整个跟踪,而不是单独处理跟踪内的span。
-
我们发现,有了这个额外的配置参数使管理我们的收集管道变得简单多了,因为我们可以很容易地在配置文件中调整我们的全局写入率这个参数。
-
如果整个跟踪过程和收集系统只使用一个采样率参数确实会简单一些,但是这就不能应对快速调整在所有部署的节点上的运行期采样率配置的这个要求。
-
我们选择了运行期采样率,这样就可以优雅的去掉我们无法写入到仓库中的多余数据,我们还可以通过调节收集系统中的二级采样率系数来调整这个运行期采样率。Dapper的管道维护变得更容易,因为我们就可以通过修改我们的二级采样率的配置,直接增加或减少我们的全局覆盖率和写入速度。
通用的Dapper工具
Dapper Depot API
-
Dapper的“Depot API”或称作DAPI,提供在Dapper的区域仓库中对分布式跟踪数据一个直接访问。
-
DAPI和Dapper跟踪仓库被设计成串联的,对Dapper仓库中的元数据暴露一个干净和直观的的接口。
- 通过跟踪ID来访问
- 全局唯一的跟踪ID读取任何一次跟踪信息。
- 批量访问
- MapReduce提供对上亿条Dapper跟踪数据的并行读取
- 用户重写一个虚拟函数,它接受一个Dapper的跟踪信息作为其唯一的参数
- 该框架将在用户指定的时间窗口中调用每一次收集到的跟踪信息。
- 索引访问
- Dapper的仓库支持一个符合我们通用调用模板的唯一索引。
- 根据通用请求跟踪特性进行绘制来识别Dapper的跟踪信息。
- 因为跟踪ID是根据伪随机的规则创建的,这是最好的办法去访问跟某个服务或主机相关的跟踪数据。
- 通过跟踪ID来访问
-
选择一个合适的自定义索引是DAPI设计中最具挑战性的部分。
-
建立一个索引的压缩过的存储只比实际数据小26%,所以消耗是巨大的。
-
最初,部署了两个索引:第一个是主机索引,另一个是服务名的索引。
-
然而,并没有找到主机索引和存储成本之间的利害关系。
-
当用户对每一台主机感兴趣的时候,他们也会对特定的服务感兴趣
-
所以最终选择把两者相结合,成为组合索引,它允许以服务名称,主机和时间戳的顺序进行有效的查找。
DAPI在Google内部的使用
- DAPI在谷歌的使用有三类:
- 持续利用DAPI的的线上Web应用
- 维护良好的可以在控制台上调用的基于DAPI的工具
- 可以被写入,运行、不过大部分已经被忘记了的一次性分析工具
- 目前有3个持久性的基于DAPI的应用程序,8个额外的按需定制的基于DAPI分析工具,以及使用DAPI框架构建的约15~20一次性的分析工具。
- 在这之后的工具就这是很难说明了,因为开发者可以构建、运行和丢弃这些项目,而不需要Dapper团队的技术支持。
Dapper的用户接口
绝大多数用户使用发生在基于web的用户交互接口。
- 用户描述的他们关心的服务和时间,和其他任何他们可以用来区分跟踪模板的信息(比如,span的名称)
- 他们还可以指定与他们的搜索最相关的成本度量(cost metric)(比如,服务响应时间)
- 一个关于性能概要的大表格,对应确定的服务关联的所有分布式处理图表
- 用户可以把这些执行图标排序成他们想要的,并选择一种直方图去展现出更多的细节
- 一旦某个单一的分布式执行部分被选中后,用户能看到关于执行部分的的图形化描述
- 被选中的服务被高亮展示在该图的中心。
- 在生成与步骤1中选中的成本度量(cost metric)维度相关的统计信息之后,提供了一个简单的直方图
- 在这个例子中,我们可以看到一个大致的所选中部分的分布式响应时间分布图。
- 用户还会看到一个关于具体的跟踪信息的列表,展现跟踪信息在直方图中被划分为的不同区域。
- 用户点击列表种第二个跟踪信息实例时,会在下方看到这个跟踪信息的详细视图(步骤5)。
- 绝大多数Dapper的使用者最终的会检查某个跟踪的情况,希望能收集一些信息去了解系统行为的根源所在
- 我们没有足够的空间来做跟踪视图的审查,但我们使用由一个全局时间轴(在上方可以看到),并能够展开和折叠树形结构的交互方式,这也很有特点。
- 分布式跟踪树的连续层用内嵌的不同颜色的矩形表示。每一个RPC的span被从时间上分解为一个服务器进程中的消耗(绿色部分)和在网络上的消耗(蓝色部分)。用户Annotation没有显示在这个截图中,但他们可以选择性的以span的形式包含在全局时间轴上。
-
为了让用户查询实时数据,用户界面能够直接与Dapper每一台生产环境下的服务器上的守护进程进行交互。
-
在该模式下,不可能指望能看到上面所说的系统级的图表展示,但仍然可以很容易基于性能和网络特性选取一个特定的跟踪。在这种模式下,可在几秒钟内查到实时的数据。
-
根据我们的记录,大约有200个不同的Google工程师在一天内使用的Dapper的UI;
-
在一周的过程中,大约有750-1000不同的用户。
-
这些用户数,在新功能的内部通告上,是按月连续的。通常用户会发送特定跟踪的连接,这将不可避免地在查询跟踪情况时中产生很多一次性的,持续时间较短的交互。
经验
在开发中使用Dapper
-
Google AdWords系统是围绕一个大型的关键词定位准则和相关文字广告的数据库搭建的。
-
当新的关键字或广告被插入或修改时,它们必须通过服务策略术语的检查。
-
当轮到从头重新设计一个广告审查服务时,这个团队迭代的从第一个系统原型开始使用Dapper,并且,最终用Dapper一直维护着他们的系统。Dapper帮助他们从以下几个方面改进了他们的服务:
- 性能
- 开发人员针对请求延迟的目标进行跟踪,并对容易优化的地方进行定位。
- Dapper也被用来确定在关键路径上不必要的串行请求并促使团队持续修复他们。
- 正确性
- 广告审查服务围绕大型数据库系统搭建。
- 系统同时具有只读副本策略(数据访问廉价)和读写的主策略(访问代价高)
- Dapper被用来在很多种情况中确定,哪些查询是无需通过主策略访问而可以采用副本策略访问。
- Dapper现在可以负责监控哪些主策略被直接访问,并对重要的系统常量进行保障。
- 理解性
- 广告审查查询跨越了各种类型的系统,包括BigTable—之前提到的那个数据库,多维索引服务,以及其他各种C++和Java后端服务。
- Dapper的跟踪用来评估总查询成本,促进重新对业务的设计,用以在他们的系统依赖上减少负载。
- 测试
- 新的代码版本会经过一个使用Dapper进行跟踪的QA过程,用来验证正确的系统行为和性能。
- 在跑测试的过程中能发现很多问题,这些问题来自广告审查系统自身的代码或是他的依赖包。
- 性能
-
广告审查团队广泛使用了Dapper Annotation API。
- Guice[13]开源的AOP框架用来在重要的软件组件上标注“@Traced”。
- 这些跟踪信息可以进一步被标注,包含:重要子路径的输入输出大小、基础信息、其他调试信息,所有这些信息将会额外发送到日志文件中。
-
同时,我们也发现了一些广告审查小组在使用方面的不足。
- 比如:他们想根据他们所有跟踪的Annotation信息,在一个交互时间段内进行搜索,然而这就必须跑一个自定义的MapReduce或进行每一个跟踪的手动检查。
- 另外,在Google还有一些其他的系统在也从通用调试日志中收集和集中信息,把那些系统的海量数据和Dapper仓库整合也不是小事情。
-
总的来说,即便如此,广告审查团队仍然对Dapper的作用进行了以下评估,通过使用Dapper的跟踪平台的数据分析,他们的服务延迟性已经优化了两个数量级。
与异常监控的集成
- Google维护了一个从运行进程中不断收集并集中异常信息报告的服务。
- 如果异常发生在Dapper跟踪采样的上下文中,那么相应的跟踪ID和span的ID也会作为元数据记录在异常报告中
- 异常监测服务的前端会提供一个链接,从特定的异常信息的报告直接导向到他们各自的分布式跟踪。
- 广告审查团队使用这个功能可以了解bug发生的更大范围的上下文。
- 通过暴露基于简单的唯一ID构建的接口,Dapper平台被集成到其他事件监测系统会相对容易。
解决延迟的长尾效应
- 考虑到移动部件的数量、代码库的规模、部署的范围,调试一个像全文搜索那样服务(第1节里提到过)是非常具有挑战性的。
- 本部分,描述了我们在减轻全文搜索的延迟分布的长尾效应上做的各种努力。
- Dapper能够验证端到端的延迟的假设,更具体地说,Dapper能够验证对于搜索请求的关键路径。
- 当一个系统不仅涉及数个子系统,而是几十个开发团队的涉及到的系统的情况下,端到端性能较差的根本原因到底在哪,这个问题即使是我们最好的和最有经验的工程师也无法正确回答。
- 在这种情况下,Dapper可以提供急需的数据,而且可以对许多重要的性能问题得出结论。
- 在调试延迟长尾效应的过程中,工程师可以建立一个小型库,这个小型库可以根据DAPI跟踪对象来推断关键路径的层级结构。
- 这些关键路径的结构可以被用来诊断问题,并且为全文搜索提供可优先处理的预期的性能改进。Dapper的这项工作导致了下列发现:
- 在关键路径上的短暂的网络性能退化不影响系统的吞吐量,但它可能会对延迟异常值产生极大的影响。
- 在图7中可以看出,大部分的全局搜索的缓慢的跟踪都来源于关键路径的网络性能退化。
- 许多问题和代价很高的查询模式来源于一些意想不到的服务之间的交互。
- 一旦发现,往往容易纠正它们,但是Dapper出现之前想找出这些问题是相当困难的。
- 通用的查询从Dapper之外的安全日志仓库中收取,并使用Dapper唯一的跟踪ID,与Dapper的仓库做关联。
- 然后,该映射用来建立关于在全局搜索中的每一个独立子系统都很慢的实例查询的列表。
- 在关键路径上的短暂的网络性能退化不影响系统的吞吐量,但它可能会对延迟异常值产生极大的影响。
推断服务依赖
-
在任何给定的时间内,Google内部的一个典型的计算集群是一个汇集了成千上万个逻辑“任务”的主机,一套的处理器在执行一个通用的方法。
-
Google维护着许多这样的集群,当然,事实上,我们发现在一个集群上计算着的这些任务通常依赖于其他的集群上的任务。
-
由于任务们之间的依赖是动态改变的,所以不可能仅从配置信息上推断出所有这些服务之间的依赖关系。
-
不过,除了其他方面的原因之外,在公司内部的各个流程需要准确的服务依赖关系信息,以确定瓶颈所在,以及计划服务的迁移。
-
Google的可称为“Service Dependencies”的项目是通过使用跟踪Annotation和DAPI MapReduce接口来实现自动化确定服务依赖归属的。
-
Dapper核心组件与Dapper跟踪Annotation一并使用的情况下,“Service Dependencies”项目能够推算出任务各自之间的依赖,以及任务和其他软件组件之间的依赖。
-
比如,所有的BigTable的操作会加上与受影响的表名称相关的标记。运用Dapper的平台,Service Dependencies团队就可以自动的推算出依赖于命名的不同资源的服务粒度。
不同服务的网络使用率
-
Google投入了大量的人力和物力资源在他的网络结构上。从前网络管理员可能只关注独立的硬件信息、常用工具及以及搭建出的各种全局网络鸟瞰图的dashboard上的信息。
-
网络管理员确实可以一览整个网络的健康状况,但是,当遇到问题时,他们很少有能够准确查找网络负载的工具,用来定位应用程序级别的罪魁祸首。
-
虽然Dapper不是设计用来做链路级的监控的,但是我们发现,它是非常适合去做集群之间网络活动性的应用级任务的分析。
-
Google能够利用Dapper这个平台,建立一个不断更新的控制台,来显示集群之间最活跃的网络流量的应用级的热点。
-
此外,使用Dapper我们能够为昂贵的网络请求提供指出的构成原因的跟踪,而不是面对不同服务器之间的信息孤岛而无所适从。建立一个基于Dapper API的dashboard总共没花超过2周的时间。
分层和共享存储系统
-
在Google的许多存储系统是由多重独立复杂层级的分布式基础设备组成的。例如,Google的App Engine[5]就是搭建在一个可扩展的实体存储系统上的。该实体存储系统在基于BigTable上公开某些RDBMS功能。
-
BigTable的同时使用Chubby[7](分布式锁系统)及GFS。再者,像BigTable这样的系统简化了部署,并更好的利用了计算资源。
-
在这种分层的系统,并不总是很容易确定最终用户资源的消费模式。例如,来自于一个给定的BigTable单元格的GFS大信息量主要来自于一个用户或是由多个用户产生,但是在GFS层面,这两种明显的使用场景是很难界定。而且,如果缺乏一个像Dapper一样的工具的情况下,对共享服务的竞争可能会同样难于调试。
-
第5.2节中所示的Dapper的用户界面可以聚合那些调用任意公共服务的多个客户端的跟踪的性能信息。这就很容易让提供这些服务的源从多个维度给他们的用户排名。(例如,入站的网络负载,出站的网络负载,或服务请求的总时间)
Dapper的救火能力(Firefighting)
-
对于一些“救火”任务,Dapper可以处理其中的一部分。“救火”任务在这里是指一些有风险很高的在分布式系统上的操作。
-
通常情况下,Dapper用户当正在进行“救火”任务时需要使用新的数据,并且没有时间写新的DAPI代码或等待周期性的报告运行。
-
对于那些高延迟,在正常负载下都会响应超时的服务,Dapper用户界面通常会把这些延迟瓶颈的位置隔离出来
-
通过与Dapper守护进程的直接通信,那些特定的高延迟的跟踪数据轻易的收集到。
-
当出现灾难性故障时,通常是没有必要去看统计数据以确定根本原因,只查看示例跟踪就足够了
-
但是,如在6.5节中描述的共享的存储服务,要求当用户活动过程中突然中断时能尽可能快的汇总信息。
-
对于事件发生之后,共享服务仍然可以利用汇总的的Dapper数据,但是,除非收集到的Dapper数据的批量分析能在问题出现10分钟之内完成,否则Dapper面对与共享存储服务相关的“救火”任务就很难按预想的那般顺利完成。
其他收获
- 首先,我们获得了超出预期的Dapper使用用例的数量,对此我们可谓欢心鼓舞。
- 另外,在除了几个的在第6节使用经验中提到过的一些用例之外,还包括资源核算系统,对指定的通讯模式敏感的服务的检查工具,以及一种对RPC压缩策略的分析器,等等。
- 一定程度上是由于我们向开发者以一种简单的编程接口的方式开放了跟踪数据存储的缘故,这使得我们能够充分利用这个大的多的社区的创造力。
- 除此之外,Dapper对旧的负载的支持也比预期的要简单,只需要在程序中引入一个用新版本的重新编译过的公共组件库(包含常规的线程使用,控制流和RPC框架)即可。
Dapper在Google内部的广泛使用还为在Dapper的局限性上提供了宝贵的反馈意见。下面我们将介绍一些我们已知的最重要的Dapper的不足:
- 合并的影响
- 我们的模型隐含的前提是不同的子系统在处理的都是来自同一个被跟踪的请求。在某些情况下,缓冲一部分请求,然后一次性操作一个请求集会更加有效。(比如,磁盘上的一次合并写入操作)。
- 在这种情况下,一个被跟踪的请求可以看似是一个大型工作单元。
- 此外,当有多个追踪请求被收集在一起,他们当中只有一个会用来生成那个唯一的跟踪ID,用来给其他span使用,所以就无法跟踪下去了。
- 我们正在考虑的解决方案,希望在可以识别这种情况的前提下,用尽可能少的记录来解决这个问题。
- 跟踪批处理负载
- Dapper的设计,主要是针对在线服务系统,最初的目标是了解一个用户请求产生的系统行为。
- 然而,离线的密集型负载,例如符合MapReduce[10]模型的情况,也可以受益于性能挖潜。
- 在这种情况下,我们需要把跟踪ID与一些其他的有意义的工作单元做关联,诸如输入数据中的键值(或键值的范围),或是一个MapReduce shard。
- 寻找根源
- Dapper可以有效地确定系统中的哪一部分致使系统整个速度变慢,但并不总是能够找出问题的根源。
- 例如,一个请求很慢有可能不是因为它自己的行为,而是由于队列中其他排在它前面的(queued ahead of)请求还没处理完。
- 程序可以使用应用级的annotation把队列的大小或过载情况写入跟踪系统。
- 此外,如果这种情况屡见不鲜,那么在ProfileMe[11]中提到的成对的采样技术可以解决这个问题。
- 它由两个时间重叠的采样率组成,并观察它们在整个系统中的相对延迟。
- 记录内核级的信息
- 一些内核可见的事件的详细信息有时对确定问题根源是很有用的。
- 我们有一些工具,能够跟踪或以其他方式描述内核的执行,但是,想用通用的或是不那么突兀的方式,是很难把这些信息到捆绑到用户级别的跟踪上下文中。
- 我们正在研究一种妥协的解决方案,我们在用户层面上把一些内核级的活动参数做快照,然后绑定他们到一个活动的span上。