概要
现代互联网服务通常被实现为复杂的、大规模的分布式系统。
这些应用程序是由软件模块的集合构建的,这些模块可能由不同的团队使用不同的编程语言开发,并且可以跨越多个物理设施的数千台机器。
在这样的环境中,有助于理解系统行为和推理性能问题的工具是非常宝贵的。
在这里,我们介绍了 Google 的生产分布式系统跟踪基础设施 Dapper 的设计,并描述了如何满足我们的低开销、应用程序级透明性以及在超大规模系统上普遍部署的设计目标。
Dapper 与其他跟踪系统(特别是 Magpie [3] 和 X-Trace [12])在概念上有相似之处,但做出的某些设计选择对其在我们的环境中取得成功至关重要,例如使用采样并将仪器限制为 相当少量的公共库。
本文的主要目标是报告我们两年多来构建、部署和使用该系统的经验,因为 Dapper 成功的最重要衡量标准是它对开发人员和运营团队的有用性。
Dapper 最初是一个独立的跟踪工具,但后来发展成为一个监控平台,支持创建许多不同的工具,其中一些工具是其设计者没有预料到的。
我们描述了一些使用 Dapper 构建的分析工具,分享了有关其在 Google 内部使用情况的统计数据,展示了一些示例用例,并讨论了迄今为止学到的经验教训。
1 简介-Introduction
我们构建 Dapper 是为了向 Google 开发人员提供有关复杂分布式系统行为的更多信息。
此类系统特别令人感兴趣,因为大量小型服务器的集合对于互联网服务工作负载来说是一个特别经济高效的平台[4]。
了解这种情况下的系统行为需要观察许多不同程序和机器上的相关活动。
网络搜索示例将说明此类系统需要解决的一些挑战。
前端服务可能会将 Web 查询分发给数百个查询服务器,每个服务器都在自己的索引部分中进行搜索。
该查询还可以被发送到许多其他子系统,这些子系统可以处理广告、检查拼写或查找专门的结果,包括图像、视频、新闻等。
所有这些服务的结果都会有选择地组合在结果页面中; 我们将此模型称为“通用搜索”[6]。
总共可能需要数千台机器和许多不同的服务来处理一个通用搜索查询。
此外,网络搜索用户对延迟很敏感,这可能是由任何子系统的性能不佳引起的。
仅查看总体延迟的工程师可能知道存在问题,但可能无法猜测哪个服务出现问题,也无法猜测为什么它表现不佳。
首先,工程师可能无法准确地知道正在使用哪些服务; 新的服务和部件可能会每周添加和修改,以添加用户可见的功能并改进性能或安全性等其他方面。
其次,工程师不会是每项服务内部的专家; 每一个都是由不同的团队构建和维护的。
第三,服务和机器可能由许多不同的客户端同时共享,因此性能工件可能是由于另一个应用程序的行为造成的。
例如,前端可以处理许多不同的请求类型,或者诸如 Bigtable [8] 之类的存储系统在跨多个应用程序共享时可能是最有效的。
上述场景对 Dapper 提出了两个基本要求:无处不在的部署和持续监控。
普遍性很重要,因为即使系统的一小部分没有受到监控,跟踪基础设施的实用性也会受到严重影响。
此外,应始终打开监视,因为通常情况下,异常或其他值得注意的系统行为很难或不可能重现。
这些要求产生了三个具体的设计目标:
• 低开销:跟踪系统对正在运行的服务的性能影响应该可以忽略不计。 在一些高度优化的服务中,即使很小的监控开销也很容易被注意到,并且可能迫使部署团队关闭跟踪系统。
• 应用程序级透明度:程序员不需要了解跟踪系统。 依赖于应用程序级开发人员的积极协作才能发挥作用的跟踪基础设施变得极其脆弱,并且经常由于仪器错误或遗漏而被破坏,因此违反了普遍性要求。 这在像我们这样的快节奏的开发环境中尤其重要。
• 可扩展性:至少需要在未来几年内处理 Google 服务和集群的规模。
另一个设计目标是跟踪数据在生成后能够快速用于分析:最好在一分钟内。
尽管基于数小时前的数据运行的跟踪分析系统仍然非常有价值,但新鲜信息的可用性可以使对生产异常做出更快的反应。
真正的应用程序级透明度,可能是我们最具挑战性的设计目标,是通过将 Dapper 的核心跟踪工具限制在一个由普遍存在的线程、控制流和 RPC 库代码组成的小语料库中来实现的。
通过使用自适应采样,可以使系统具有可扩展性并降低性能开销,这将在 4.4 节中进行描述。
生成的系统还包括用于收集跟踪的代码、用于可视化跟踪的工具以及用于分析大量跟踪的库和 API(应用程序编程接口)。
尽管 Dapper 有时足以让开发人员识别性能异常的根源,但它并不打算取代所有其他工具。
我们发现 Dapper 的系统范围数据通常侧重于性能调查,以便可以在本地应用其他工具。
1.1 贡献总结 Summary of contributions
分布式系统跟踪工具的设计空间已经在之前的许多优秀文章中进行了探讨,其中与 Dapper 关系最密切的是 Pinpoint [9]、Magpie [3] 和 X-Trace [12]。
这些系统往往在其开发的早期阶段就在研究文献中进行描述,然后才有机会清楚地评估重要的设计选择。
由于 Dapper 已经大规模生产和运营多年,我们认为最合适的做法是将本文的重点放在 Dapper 的部署教会了我们什么、我们的设计决策如何发挥作用以及它在哪些方面最有用。
Dapper 作为性能分析工具开发平台的价值,以及其本身的监控工具的价值,是我们在回顾性评估中可以识别的少数意外结果之一。
尽管 Dapper 与 Pinpoint 和 Magpie 等系统共享许多高级思想,但我们的实现在该领域包含许多新的贡献。
例如,我们发现采样对于降低开销是必要的,特别是在高度优化的 Web 服务中,这些服务往往对延迟非常敏感。
也许更令人惊讶的是,我们发现数千个请求中的一个样本就可以为跟踪数据的许多常见用途提供足够的信息。
我们系统的另一个重要特征是我们能够实现的应用程序级透明度。
我们的仪器被限制在软件堆栈中足够低的水平,即使是像谷歌网络搜索这样的大规模分布式系统也可以在没有额外注释的情况下被追踪。
虽然这更容易实现,因为我们的部署环境具有一定程度的同质性,但我们这样做的结果表明了实现这种透明度的一些充分条件。
2 Dapper 中的分布式跟踪
分布式服务的跟踪基础设施需要记录有关代表给定发起者在系统中完成的所有工作的信息。
例如,图 1 显示了具有 5 台服务器的服务:一个前端 (A)、两个中间层(B 和 C)以及两个后端(D 和 E)。
当用户请求(在本例中为发起者)到达前端时,它会向服务器 B 和 C 发送两个 RPC。
B 可以立即响应,但 C 需要后端 D 和 E 的工作才能回复 A,而 A 又响应原始请求。
此请求的一个简单但有用的分布式跟踪是每个服务器发送和接收的每条消息的消息标识符和时间戳事件的集合。
已经提出了两类解决方案来聚合这些信息,以便可以将所有记录条目与给定的发起者(例如图 1 中的 RequestX)、黑盒和基于注释的监控方案相关联。
黑盒(Black-box)方案 [1,15,2] 假设除了上述消息记录之外没有其他信息,并使用统计回归技术来推断该关联。
基于注释(Annotation-based)的方案 [3,12,9,16] 依赖应用程序或中间件使用全局标识符显式标记每个记录,将这些消息记录链接回原始请求。
虽然黑盒方案比基于注释的方法更易于移植,但由于它们依赖于统计推断,因此需要更多数据才能获得足够的准确性。
显然,基于注释的方法的主要缺点是需要对程序进行检测。
在我们的环境中,由于所有应用程序都使用相同的线程模型、控制流和 RPC 系统,因此我们发现可以将检测限制为一小组公共库,并实现对应用程序开发人员有效透明的监控系统。
我们倾向于将 Dapper 跟踪视为嵌套 RPC 树。
然而,我们的核心数据模型并不局限于我们特定的 RPC 框架; 我们还跟踪 Gmail 中的 SMTP 会话、来自外部世界的 HTTP 请求以及对 SQL 服务器的出站查询等活动。
正式地,我们使用树(trees)、跨度(spans)和注释(annotations)对 Dapper 跟踪进行建模。
2.1 追踪树和跨度
在 Dapper 跟踪树中,树节点是基本工作单元,我们将其称为跨度。
边指示跨度与其父跨度之间的关系(casual relationship)。
然而,无论它在更大的跟踪树中的位置如何,跨度也是带时间戳的记录的简单日志,这些记录对跨度的开始和结束时间、任何 RPC 计时数据以及零个或多个特定于应用程序的注释进行编码,如第 2.3 节中所述。
我们在图 2 中说明了跨度如何形成较大迹线的结构。
Dapper 记录每个跨度的人类可读的跨度名称,以及跨度 ID 和父 ID,以便重建单个分布式跟踪中各个跨度之间的因果关系。
没有父 ID 创建的 Span 称为根 Span。
与特定跟踪关联的所有跨度也共享一个公共跟踪 ID(图中未显示)。
所有这些 id 都是概率上唯一的 64 位整数。
ps: 这里的 id 如果只是唯一,那么算法会更加灵活一下。
在典型的 Dapper 跟踪中,我们期望为每个 RPC 找到一个跨度,并且每个额外的基础设施层都会为跟踪树添加一个额外的深度级别。
图 3 提供了典型 Dapper 跟踪范围中记录的事件的更详细视图。