最近在研究分布式链路跟踪系统,Google Dapper 当然是必读的论文了,目前网上能搜到一些中文翻译版,然而读下来个人感觉略生硬;这里试着在前人的肩膀上重新翻译一遍这个论文,权当是个人的学习笔记,如果同时能给其他人带来好处那就更好了。
同时把译文放到了 github,如您发现翻译错误或者不通顺之处,恳请提交 github PR: https://github.com/AlphaWang/alpha-dapper-translation
摘要
现代互联网服务通常都是复杂的大规模分布式系统。这些系统由多个软件模块构成,这些软件模块可能由不同的团队开发、可能使用不同的编程语言实现、可能布在横跨多个数据中心的几千台服务器上。这种环境下就急需能帮助理解系统行为、能用于分析性能问题的工具。
本文将介绍 Dapper 这个在 Google 生产环境下的分布式系统跟踪服务的设计,并阐述它是如何满足在一个超大规模系统上达到低损耗(low overhead)、应用级透明(application-level transparency)、大范围部署(ubiquitous deployment)这三个需求的。Dapper 与其他一些跟踪系统的概念类似,尤其是 Magpie[3] 和X-Trace[12],但是我们进行了一些特定的设计,使得 Dapper 能成功应用在我们的环境上,例如我们使用了采样并将性能测量(instrumentation)限制在很小一部分公用库里。
本文的主要目的是汇报两年多以来我们构建、部署并应用 Dapper 的经历,这两年多里 Dapper 对开发和运维团队非常有用,取得了显著的成功。最初 Dapper 只是一个自包含(self-contained)的跟踪工具,后来演化成了一个监控平台并促生出许多不同的工具,有些工具甚至 Dapper 的设计者都未曾预期到。我们将介绍一些基于 Dapper 构造的分析工具,分享这些工具在 Google 内部使用的统计数据,展示一些使用场景的例子,并讨论我们学习到的经验教训。
1 介绍
Dapper 的目的是为了将复杂分布式系统的更多行为信息提供给 Google 开发者。这种分布式系统利用大规模的小服务器,通常对于互联网服务是一个非常经济的平台,所以很受关注。要理解在这种上下文中要的系统行为的话,就需要观察横跨不同程序和不同机器的关联行为。
下面基于一个 web 搜索的例子来说明这种系统需要应对哪些挑战。前端服务器将一个 web 查询分发给上百台搜索服务器,每个搜索服务器在自己的 index 中完成搜索。同时这个 web 查询可能还会被发送给多个其他子系统,进行广告处理、拼写检查、查找相关的图片/视频/新闻等。所有这些服务的结果会被有选择地合并成结果页面;我们把这种模型称之为全局搜索 (universal search)
。处理一次全局搜索查询,总计需要上千台机器,涉及多种服务。而且 web 搜索的用户对延时很敏感,而任何一个子系统的性能差了都可能导致延时。工程师如果只看总体耗时的话,他能知道出问题了,但是他猜不到是哪个系统出问题、为什么出问题。首先,工程师可能无法准确知道到底调用了哪些服务;每周我们都会添加新的服务,用于实现用户需求、提升性能或安全性。其次,工程师不可能对每个服务的内部都了如指掌;每个服务都是由不同的团队开发维护的。第三,服务和服务器可能被许多不同的客户端调用,所以性能问题有可能是其他应用造成的。举例来说,前端服务器可能要处理多个不同的请求类型,或者类似 Bigtable 这种存储系统在被多个应用共享时效率最高。
上面描述的场景就对 Dapper 提出了两条最基本的要求:大范围部署 (uniquitous deployment)、持续监控 (continuous monitoring)。即便只有很小一部分系统没有被监控到,跟踪系统的作用也会大打折扣,所以大范围部署非常重要。另外,应该始终开启监控,因为通常来说异常系统行为很难重现,甚至根本无法重现。这两条基本要求提出了三个具体的设计目标:
- 低消耗 (Low overhead):跟踪系统对在线服务的性能影响应该做到可忽略不计。对于一些高度优化过的服务,监控系统的一点小消耗都会很显眼,都可能迫使部署团队不得不关停跟踪系统。
- 应用级透明 (Application-level transparency):程序员应该不需要感知到跟踪系统。如果跟踪系统要求应用开发者的配合才能生效,那么这个跟踪系统就太脆弱了,经常会由于应用侵入代码的 bug 或者疏忽导致无法正常工作,这就违反了"大范围部署"的要求。这在我们这种快速开发的环境下尤为重要。
- 可扩展性 (Scalability):需要能处理 Google 在未来几年的服务和集群规模。
另外一个设计目标是生成跟踪数据后要很快可用于分析:最好是在一分钟内。尽管一个能处理几小时前数据的跟踪分析系统已经很有用了,但是能分析最新数据的话会让我们能对生产环境的异常情况作出快速反应。
我们通过把 Dapper 跟踪植入的核心代码限制在线程调用、控制流以及 RPC 等库代码中,实现了真正的应用透明这个最具挑战性的目标。使用自适应的采样(见4.4节),我们做到了可扩展性、降低性能损耗。最终的系统还包括收集跟踪数据的代码、可视化数据的工具、用于分析大规模跟踪数据的库和 API。尽管开发人员有时通过 Dapper 就足以找出性能问题的根源,但 Dapper 并不会替代所有其他的工具。我们发现 Dapper 的数据往往侧重于性能排查,所以其他工具也有自己的用处。
1.1 贡献总结
之前已有一些优秀的文章探讨了分布式系统跟踪工具的设计空间,其中 Pinpoint[9]、Magpie[3] 和 X-Trace[12] 与 Dapper 最为相关。这些系统倾向于在开发过程中的早期就写成研究报告,而此时还没有机会明确地评估重要的设计选型。Dapper 已经在生产环境中被大型系统应用好几年了,我们认为本文最适合的侧重点是讨论我们在 Dapper 开发过程中有哪些收获、我们的设计决策是如何制定的、它在哪些方面最有用。Dapper 作为一个开发性能分析工具的平台以及作为一个监控工具,其价值是我们可以在回顾评估中找到一些意想不到的产出。
虽然 Dapper 的许多高层理念和 Pinpoint、Magpie 等其他系统是共通的,但是我们的实现包含了一系列新的贡献。举个例子,我们发现要想降低消耗的话采样就必不可少,尤其是在高度优化后的对延迟非常敏感的 web 服务中。或许最令人惊讶的是,我们发现即便只使用 1/1000 的采样率,已经能为跟踪数据的通用用例提供足够多的信息了。
Dapper 的另一个重要特征是我们实现的应用透明程度非常高。我们将性能测量限制在足够底层,所以即便是像 Google web 搜索这样的大型分布式系统也能进行跟踪,而无需额外的注解。虽然由于我们的部署环境具有一定的同质性,所以更容易实现应用透明这个目标,但是我们的结果也论证了实现透明性的充分条件。
2 Dapper 的分布式跟踪
分布式服务的跟踪系统需要记录在一次请求后系统完成的所有工作的信息。举个例子,图-1展示了拥有 5 台服务器的服务:一个前端服务器 A,两个中间层 B 和 C,两个后端服务器 D 和 E。当用户发起请求到前端服务器 A 之后,会发送两个 RPC 调用到 B 和 C。B 马上会返回结果,但是 C 还需要继续调用后端服务器 D 和 E,然后返回结果给 A,A 再响应最初的请求。对这个请求来说,一个简单的分布式跟踪系统需要记录每台机器上的每次信息发送和接收的信息标识符和时间戳。
(图-1. 由用户请求X 发起的穿过一个简单服务系统的请求路径。字母标识的节点表示分布式系统中的处理过程)
为了能将信息聚合到一起以便人们能将所有记录信息关联到一个初始请求(如图1中的请求 X),我们提出了两种解决方案:黑盒监控模式
和 基于标注的监控模式
。黑盒模式[1, 15, 2] 假定除了上面描述的信息记录之外无需任何额外的信息,而使用统计回归技术来推断关联关系。基于标注的模式[3, 12, 9, 16] 则要求应用程序或中间件显式地将每个记录关联到一个全局 ID,从而将这些信息记录关联回初始请求。黑盒模式比基于标注的模式更加轻便,但是它依赖统计推断,所以需要更多的数据以便获取足够的准确性。很明显,基于标注的模式关键缺点是需要有代码侵入。在我们的环境中,由于所有应用系统都使用相同的线程模型、控制流和 RPC 系统,所以我们可以将性能测量限制在小规模的公用库中,以此实现对开发人员有效透明的监控系统。
我们倾向于认为 Dapper 的跟踪是一个嵌入式的 RPC 树。然而,我们的核心数据模型并不局限于特定的RPC 框架;我们也能跟踪例如 Gmail SMTP 会话、来自外界的 HTTP 请求、对 SQL 服务器的查询等行为。正式一点说,Dapper 跟踪模型使用了树
、span
和 标注
。
2.1 跟踪树与span
在 Dapper 跟踪树中,树节点是基本单元,我们称之为 span
。节点之间的连线表示 span 与其父span
之间的关系。虽然节点在整个跟踪树中的位置是独立的,但 span 也是一个简单的时间戳日志,其中编码了这个 span 的开始时间、结束时间、RPC 时间数据、以及0或多个应用程序相关的标注,我们将在 2.3 节讨论这些内容。
(图-2. Dapper 跟踪树中5个 span 的因果和实时关系)
图2 阐释了 span 是如何构造成更大的跟踪结构的。Dapper 为每个 span 记录了一个可读的span name
、span id
和 parent id
,这样就能重建出一次分布式跟踪过程中不同 span 之间的关系。没有parent id 的 span被称为 根span
。一次特定跟踪的所有相关 span 会共享同一个通用的trace id
(trace id在图中没有绘出)。所有这些 ID 可能是唯一的 64 位整数。在一个典型的 Dapper 跟踪中,我们希望每个 RPC 对应一个 span,每一个组件层对应跟踪树上的一个层级。
(图-3. span 的详细视图)
图3 给出了 Dapper 跟踪 span 中记录的事件的更详细视图。这个 span 标示图 2 中更长的那次 Helper.Call
RPC 调用。Dapper 的 RPC 库记录下了 span 的开始时间和结束时间、RPC 的计时信息。如果应