开源项目
概要
现代互联网服务通常被实现为复杂的、大规模的分布式系统。
这些应用程序是由软件模块的集合构建的,这些模块可能由不同的团队使用不同的编程语言开发,并且可以跨越多个物理设施的数千台机器。
在这样的环境中,有助于理解系统行为和推理性能问题的工具是非常宝贵的。
在这里,我们介绍了 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 跟踪范围中记录的事件的更详细视图。
这个特定的跨度描述了图 2 中两个"Helper.Call"RPC 中较长的一个。
Span 的开始和结束时间以及任何 RPC 计时信息均由 Dapper 的 RPC 库工具记录。
如果应用程序所有者选择使用自己的注释来增强跟踪(如图中的"foo"注释),这些注释也会与跨度数据的其余部分一起记录。
需要注意的是,一个跨度可以包含来自多个主机的信息; 事实上,每个 RPC 跨度都包含来自客户端和服务器进程的注释,这使得双主机跨度成为最常见的跨度。
由于客户端和服务器上的时间戳来自不同的主机,因此我们必须注意时钟偏差。
在我们的分析工具中,我们利用了这样一个事实:RPC 客户端总是在服务器接收请求之前发送请求,反之亦然,对于服务器响应也是如此。
这样,我们就有了 RPC 服务器端跨度时间戳的下限和上限。
2.2 检测点(Instrumentation points)
Dapper 几乎完全依赖于一些通用库的检测,能够遵循分布式控制路径,应用程序开发人员几乎零干预:
• 当线程处理跟踪的控制路径时,Dapper 将跟踪上下文附加到线程本地存储。
跟踪上下文是一个小型且易于复制的跨度属性(例如跟踪和跨度 ID)容器。
• 当计算延迟或异步时,大多数Google 开发人员使用通用控制流库来构造回调并将其安排在线程池或其他执行器中。
Dapper 确保所有此类回调存储其创建者的跟踪上下文,并且在调用回调时此跟踪上下文与适当的线程关联。
通过这种方式,用于跟踪重建的 Dapper id 能够透明地遵循异步控制路径。
• 几乎所有 Google 进程间通信都是围绕单个 RPC 框架构建的,并绑定了 C++ 和 Java。
我们已经对该框架进行了检测,以定义所有 RPC 的跨度。
对于跟踪的 RPC,跨度和跟踪 ID 从客户端传输到服务器。
对于基于 RPC 的系统(例如 Google 广泛使用的系统),这是一个重要的检测点。
我们计划随着非 RPC 通信框架的发展和找到用户群而对其进行检测。
Dapper 跟踪数据与语言无关,生产中的许多跟踪组合了来自用 C++ 和 Java 编写的流程的数据。
在第 3.2 节中,我们讨论了我们在实践中能够实现的应用程序透明度水平。
2.3 注释(Annotations)
上述检测点足以导出复杂分布式系统的详细跟踪,使核心 Dapper 功能可用于其他未经修改的 Google 应用程序。
然而,Dapper 还允许应用程序开发人员使用附加信息来丰富 Dapper 跟踪,这些信息可能有助于监视更高级别的系统行为或帮助调试问题。
我们允许用户通过一个简单的 API 定义带时间戳的注释,其核心如图 4 所示。
这些注释可以具有任意内容。
为了保护 Dapper 用户免受意外的过度记录,各个跟踪范围的总注释量有一个可配置的上限。
无论应用程序行为如何,应用程序级注释都无法取代结构跨度或 RPC 信息。
除了简单的文本注释之外,Dapper 还支持键值注释映射,为开发人员提供更多跟踪能力,例如维护计数器、记录二进制消息以及在进程内传输任意用户定义的数据以及跟踪请求。
这些键值注释用于在分布式跟踪的上下文中定义特定于应用程序的等价类。
2.4 采样(Sampling)
低开销是 Dapper 的一个关键设计目标,因为如果服务运营商对性能有任何重大影响,那么服务运营商将不愿意部署尚未证实价值的新工具,这是可以理解的。
此外,我们希望允许开发人员使用注释 API,而不必担心额外的开销。
我们还发现某些类别的 Web 服务确实对检测开销很敏感。
因此,除了使 Dapper 集合的基本检测开销尽可能小之外,我们还通过仅记录所有跟踪的一小部分来进一步控制开销。
我们在 4.4 节中更详细地讨论了这种跟踪采样方案。
2.5 痕迹收集(Trace collection)
Dapper 跟踪日志记录和收集管道是一个三阶段过程(参见图 5)。
首先,将跨度数据写入 (1) 到本地日志文件。
然后,Dapper 守护进程和收集基础设施将其从所有生产主机中拉出 (2),最后将其写入 (3) 到几个区域 Dapper Bigtable [8] 存储库之一的单元中。
跟踪被布置为单个 Bigtable 行,每列对应一个跨度。
Bigtable 对稀疏表布局的支持在这里很有用,因为各个跟踪可以具有任意数量的跨度。
跟踪数据收集的中值延迟(即数据从检测的应用程序二进制文件传播到中央存储库所需的时间)小于 15 秒。
随着时间的推移,第 98 个百分位数的延迟本身是双峰的; 大约 75% 的时间,第 98 个百分点的收集延迟小于两分钟,但其他大约 25% 的时间可能会增长到几个小时。
Dapper 还提供了一个 API 来简化对我们存储库中跟踪数据的访问。
Google 的开发人员使用此 API 来构建通用和特定于应用程序的分析工具。
5.1 节包含迄今为止有关其用法的更多信息。
2.5.1 带外跟踪收集(Out-of-band trace collection)
所描述的 Dapper 系统使用请求树本身执行带外跟踪记录和收集。
这样做是出于两个不相关的原因。
首先,带内收集方案(其中跟踪数据在 RPC 响应标头内发送回)可能会影响应用程序网络动态。
在 Google 的许多大型系统中,找到具有数千个跨度的痕迹并不罕见。
然而,RPC 响应(即使在如此大的分布式跟踪的根附近)仍然相对较小:通常小于 10 KB。
在此类情况下,带内 Dapper 跟踪数据会使应用程序数据相形见绌,并使后续分析的结果产生偏差。
其次,带内收集方案假设所有 RPC 都是完美嵌套的。
我们发现有许多中间件系统在它们自己的所有后端返回最终结果之前就将结果返回给调用者。
带内(in-band)收集系统无法解释这种非嵌套分布式执行模式
2.6 安全和隐私考虑
记录一定量的 RPC 有效负载信息将丰富 Dapper 跟踪,因为分析工具可能能够在有效负载数据中找到可以解释性能异常的模式。
然而,在某些情况下,有效负载数据可能包含不应向未经授权的内部用户(包括从事性能调试的工程师)透露的信息。
由于安全和隐私问题是不容协商的,Dapper 存储 RPC 方法的名称,但此时不记录任何有效负载数据。
相反,应用程序级注释提供了一种方便的选择加入机制:应用程序开发人员可以选择将其认为对以后分析有用的任何数据与跨度相关联。
Dapper 还提供了一些其设计者没有预料到的安全优势。
例如,通过跟踪公共安全协议参数,Dapper 用于监控应用程序是否通过适当级别的身份验证或加密来满足安全策略。
Dapper 还可以提供信息以确保按预期实施基于策略的系统隔离,例如承载敏感数据的应用程序不会与未经授权的系统组件交互。
这些类型的测量比源代码审计提供了更大的保证。
小结
希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。
我是老马,期待与你的下次相遇。