Skip to content

Lec 16 性能调试

X-Trace: A Pervasive Network Tracing Framework, NSDI'07

TraceWeaver: Distributed Request Tracing for Microservices Without Application Modification, sigcomm'24

Sage: Practical & Scalable ML-Driven Performance Debugging in Microservices

论文阅读: X-Trace

论文阅读:TraceWeaver

摘要

监控和调试现代云应用是一项挑战,原因是一个简单的API可能涉及多个关联分布式微服务。为了提供这样复杂系统的观测点,分布式跟踪框架会提总微服务的调用树。然而,此类解决方案需要对分布式应用的每个组件进行插桩,以添加和传播追踪头部信息,所以在成熟的系统很难被采用。本文探讨了是否可以在不进行任何应用插桩的情况下追踪请求,我们称之为请求追踪重构(request trace reconstruction)。为此,我们开发了 TraceWeaver 系统,该系统利用生产环境中易于获取的信息(如时间戳)和测试环境中的信息(如调用图)来重构请求追踪,达到实用高精度。TraceWeaver 的核心是一个重构算法,该算法利用请求-响应时间戳有效剪枝搜索空间,并应用统计时间分析技术来重构追踪。对(1)基准微服务应用和(2)生产微服务数据集的评估表明,TraceWeaver 可实现约 90% 的高精度,并可有效应用于多种场景(如识别慢服务和 A/B 测试)

1. 介绍

微服务带来了很多便利,巴拉巴拉。但不幸的是,这种高度分布式的应用在运行和调试上具有挑战性。例如,运维人员可能希望确定哪个服务导致了特定用户面向 API 调用延迟增加。一个 API 调用生成的 API 调用树可使得隔离特定问题的组件变得非常困难。为进行此类故障排查,请求追踪(request trace)不可或缺。追踪中的每条记录包括开始和结束时间(称为“span”)以及指向其子请求记录的指针。分布式追踪框架如 Jaeger [35] 和 Zipkin [63],以及商业解决方案如 Instana [33] 和 Datadog [23],以及早期的研究提案如 XTrace [27],帮助开发者应对上述问题。由于网络通信是外部可见的,此类框架可以轻松自动记录微服务之间的请求作为孤立事件,即单个 span(individual spans)。但要生成完整的请求追踪,这些系统要求应用开发者对其代码进行插桩(instrument their code),携带标识符以关联每个请求及其子请求,这被称为上下文传播(context propagation)。即使在单一服务内部,也需要在模块级别进行插桩以跟踪执行流程,要求在调用模块时将上下文对象作为参数传递。

无论应用使用何种底层通信框架(如 Apache Thrift [1]、gRPC [2] 等),请求追踪都需要对应用级代码进行修改。只有应用级模块能够跟踪执行流程(即哪个传入请求触发了哪些传出请求)。这些特定于上下文的信息无法在没有应用支持的情况下自动提供给底层通信框架。进行上下文传播的代码修改可能需要投入大量时间,尤其是对于由不同团队编写的数十或数百个微服务而言。某些微服务修改起来可能更具挑战性(例如,遗留应用),甚至完全无法修改(例如,专有的二进制代码),导致请求追踪不完整。即使修改可行,添加追踪插桩也常常被埋没在长长的功能请求列表中。这是限制请求追踪采用的一个主要因素。

在本文中,我们提出一个问题:无需对应用进行插桩,能否以高精度生成请求追踪?我们将此问题称为非侵入式请求追踪(non-intrusive request tracing)。

首先,无法构建完全准确的请求追踪,但即使是精度足够高的近似请求追踪也极具价值。许多性能分析任务不需要每个单独追踪都完全正确;用户通常希望了解请求子集的行为,例如,最差 5% 端到端延迟的请求在每个服务的处理延迟是多少?或者,高优先级用户的延迟敏感查询的典型性能特征是什么?即使个别追踪偶尔有误,这些问题仍可得到有意义的回答。

第二个观察是,现代微服务环境提供了收集有用信息的新途径,可用于非侵入式请求追踪。尽管我们将应用本身视为不可修改,但可以通过服务网格 sidecar 或 eBPF 钩子观察应用的通信细节,例如,映射传入请求到对应的传出响应,并获取请求-响应时间戳。此外,现代 Kubernetes 环境的应用打包使得在测试环境中启动应用变得可行,从而以可控方式观察应用。我们可以利用测试环境推断调用图,即某个服务的传入请求调用的后台服务序列。

我们开发了 TraceWeaver 系统,利用上述信息(span 时间、调用图)进行非侵入式请求追踪,达到实用高精度。TraceWeaver 的核心是一个追踪重构算法,结合调用图和 span 时间的约束来剪枝候选搜索空间,并使用软统计时间启发式方法将每个服务的传入请求映射到可能的子请求。具体而言,我们估计父子请求之间的时间分布,用其为可行的父子映射评分,然后通过将其编码为最大独立集问题选择最高评分的映射。但存在一个先有鸡还是先有蛋的问题——在不知道映射的情况下,如何获知父子请求的时间分布?我们通过迭代联合估计过程解决此问题,同时发现时间分布和映射,快速收敛到准确结果。此外,我们扩展方法以适应调用图的有限动态变化。

在使用 DeathStarBench [28] 基准测试套件的评估中,最佳基线方法的重构精度为 70%,而我们的技术将其提升至 93%。对来自阿里巴巴[12]的生产数据集(运行多个面向客户的应用程序)的初步分析显示,即使在高负载和可变条件下,精度也在 80%-99% 范围内。尽管不完美,TraceWeaver 的追踪重构已可用于多种任务。我们展示了两个用例:确定哪个后台服务导致特定 API 调用子集变慢,以及在 A/B 测试中检测服务性能特征的变化。

2. 问题描述

2.1 术语

image-20250603135550076

我们参考图 1(表示基于微服务的应用)来解释本文中使用的术语。

  • 跨度(Span):一个 span 是一个请求-响应对,表示在运行的服务实例上执行一次 API 调用,包含元数据,如调用者、被调用者、开始时间、结束时间和 API 端点(客户端发送请求以与服务器功能交互的特定 URL [56])。在上述示例中,(r1, r8)、(r2, r5) 等是 span。
  • 调用图(Call Graph):在每个服务 X 处,调用图描述了 X 为响应传入请求而联系的其他服务。在图 1 中,根据调用图,服务 A 与服务 B、 C 通信以处理请求。
  • 依赖顺序(Dependency Order):在每个服务 X 处,依赖顺序指后台调用的执行顺序。在上述示例中,服务 A 的依赖顺序如下:为响应传入请求,A 按顺序调用 B 和 C,收到 C 的响应后返回响应。类似地,服务 B 的调用图是:为响应请求,B 并行调用 D 和 E,并在收到 D 和 E 的响应后返回响应。
  • 父子关系(Parent-child Relationship):父子关系 r1→r2 表示在处理 r1 的过程中,微服务调用了 r2。在上述示例中,为处理和响应传入请求 r1,服务 A 调用服务 B (r2),收到 B 的响应 r5 后,再调用服务 C (r6),收到 C 的响应 r7 后,A 进行自身处理并返回响应 r8 给用户。因此,r2 和 r6 是 r1 的子请求。同样,在服务 B 处,后续请求 r3 和 r3'('表示并行调用)是 r2 的子请求。
  • 请求追踪(Request Trace):请求 r 的追踪包括 r 及其响应,以及其所有后代请求的完整树形记录。追踪中每个请求的记录包括其 span 信息(即调用者、被调用者、API 端点、开始和结束时间)以及指向其子请求记录的指针。在上述示例中,根 span 从请求 r1 到达前端服务节点 A(通常由外部客户端调用)开始,到返回相应的响应 r8 结束。其余 span 涉及后续的请求-响应对,例如 r2-r5、r3-r4 等,这些是 r1-r8 对的子 span。
  • 可见信息:在上述信息中,我们可以轻松获取 span 元数据(通过 eBPF、服务网格 sidecar 等)。我们还可以从生产数据或隔离测试环境中推断调用图。如何获取这些信息将在 §5 中详细说明。然而,父子关系及由此衍生的请求追踪是不可见的。这是本文问题的核心,我们将在下一节详细讨论。

2.2 请求追踪

本文针对的核心问题是:是否可以构建完整的请求追踪,即将每个服务收到的请求与其对应的子请求进行映射。

为什么请求追踪有用?对于各种调试和故障排查任务至关重要。例如,假设运维人员希望了解在,一小部分的关键请求,导致这些重要请求变慢。要回答这个问题,必须能够映射出每个高优先级请求在每个服务中触发的后台请求。

为什么基于插桩的请求追踪困难?跟踪请求在应用中历程的一种方法是对所有服务组件进行插桩,使附加到传入请求的唯一上下文标识符被传递到因该传入请求而产生的后台服务请求上。这种上下文传播必须由应用逻辑本身支持,因为只有应用才能跟踪跨函数边界的执行流程。底层框架(如服务网格或 RPC 框架)无法提供此功能,因为它们缺乏对应用逻辑及其内部执行流程的可见性。虽然复制标识符看似简单,但实际操作面临重大障碍:由不同团队或供应商维护的所有软件组件都必须正确传播上下文。这需要就一致使用的 API 达成共识,更重要的是,需要对微服务内部代码进行插桩,以在函数调用和数据结构中传递标识符。

OpenTelemetry [43] 定义了标准 API,分布式追踪框架如 Jaeger 和 Zipkin(以及其他商业解决方案)提供了上下文传播的插桩工具,供开发者在常用库或通过显式调用的钩子使用。

现有工作如 vPath [54] 和 DeepFlow [51] 针对一组有限的应用解决了非侵入式请求追踪问题,假设应用采用特定的线程模型。具体来说,假设线程模型没有线程间的交接和异步调用,因此线程的每次传出请求可以映射到该线程捕获的最新网络事件(请求/响应)。基于这一假设,vPath(和 DeepFlow)可以记录捕获请求 ri 的线程 t,并将 t 发出的后续请求 rj 关联到 ri,直到 t 捕获下一个请求。虽然这种方案对遵循此线程模型的应用有效,但这些假设对多种常见应用类型不成立,例如将请求交给 RPC 库(如 Thrift [1] 或 gRPC [2])通信线程的应用,或使用事件驱动、异步通信进行非阻塞 I/O 操作的应用(如 Node.js [8] 应用)。例如,如图 2b 所示,由于非阻塞磁盘 I/O 行为(1b、2b),在第二种情况下,“下一个”(第一个)传出事件是由于第一个请求(1a)触发的传出后台请求(1c),尽管第二个请求(2a)是最后一个未完成的传入请求。vPath(和 DeepFlow)在这种情况下会错误地将 2a 映射到 1c。

非侵入式请求追踪(non-intrusive request tracing)。这是我们工作的主题。虽然服务网格 [34] 和 eBPF [26] 等基础设施技术在可观察性方面很有价值,但它们不足以解决追踪挑战。服务网格 sidecar 处理通信功能 [13],如负载均衡和重试,并可拦截所有流量,从而无需修改应用代码即可观察/修改请求。另一方面,eBPF 可以在操作系统内核中运行程序,允许以最小开销监控/操作系统操作,如网络系统调用。尽管这些技术很有用,但它们单独无法解决追踪问题,因为如果 sidecar(或 eBPF 程序)为传入请求添加自定义头部(如图 2a 所示),无法保证应用会将这些头部传播到相关的传出后台请求。没有这些头部的传播,关联请求变得不可能。此外,仅拥有调用图(应用程序为请求查询的后台服务及其顺序的集合)也不够。这种局限性源于多个请求可能同时遍历相同的调用图,仅基于调用图知识难以区分请求。

image-20250603165813675

3. Tracewarer 概述

我们列举了 TraceWeaver 的各个组件(见图 3):image-20250603170443233

获取输入数据。 为了重建请求追踪,TraceWeaver 需要来自正在运行的应用的实时 span 数据,以及这些 span 所遵循的调用图和依赖顺序(§2.1)。TraceWeaver 使用 eBPF 挂钩网络系统调用(如 acceptrecvsendclose)来收集实时的 span 信息,包括调用方、被调用方、API 端点、开始时间和结束时间。调用图和依赖顺序可以由运维人员直接提供(可选),也可以通过在测试环境中运行采集到的 span 自动推导出来。详见第 5 节。

预处理。如果运维人员提供了调用图,TraceWeaver 会通过分析它们识别依赖关系。如果没有提供,则使用测试环境中生成的 span 来学习依赖关系。借助这些 span,TraceWeaver 可以学习应用在每个服务上的两类依赖信息:

  • (a) 调用图:该服务调用了哪些后端服务;
  • (b) 依赖顺序:该服务调用这些后端服务的顺序。

这些依赖关系构成了约束条件,使 TraceWeaver 能够识别出每个请求的候选映射(即符合依赖约束的所有可能的出站请求集合)。预处理仅需运行一次,只有在应用更新后才需要重新运行。第 5 节提供了更多细节。

追踪重建。在请求到达率足够高的情况下,每个服务收到的一个请求可能对应多个不同的候选出站请求(即该服务对它所调用的后端服务发出的请求)。TraceWeaver 的追踪重建算法(详见第 4 节)会对这些候选项进行评分和排序,以找到最优的映射关系。

使用重建结果。虽然单个重建后的请求追踪可以用于人工分析,但提取应用性能的聚合统计信息是非常实用的另一个使用场景。为此,运维人员可以指定一个过滤器,选出一部分映射追踪,这些追踪共同构成一个“聚合”追踪,表示该子集的整体行为。很多实用场景可以基于这种聚合追踪实现。例如:

  • 分析尾部延迟的追踪,识别造成延迟膨胀的拖后腿服务(straggler service);
  • 运行 A/B 测试,对比某些使用了版本 B 而不是版本 A 的请求性能差异。

第 6.4 节展示了这些用例。

部署模式。TraceWeaver 支持离线在线两种部署模式:

  • 离线模式下,span 被收集和存储起来,按需进行分析;
  • 在线模式下,span 会被实时传递给正在运行的 TraceWeaver 实例,用于实时构建请求追踪(详见 §5.3)。

论文阅读: Sage

摘要

云应用正在逐步从大型的单体服务转向由多个松耦合微服务组成的复杂图结构。尽管微服务架构带来了模块化和弹性等优势,但它也使集群管理和性能调试变得更加复杂,因为服务之间的依赖关系可能引发反压(backpressure)级联式的服务质量(QoS)违约

以往对云服务性能调试的研究,要么依赖经验性技术,要么使用有监督学习来诊断性能问题的根本原因——这通常需要对应用进行大量插桩,难以在实际部署中落地。

我们提出了 Sage,一个面向交互式云微服务的机器学习驱动的根因分析系统,其设计注重实用性和可扩展性。Sage 利用无监督机器学习模型来避免对追踪数据进行人工标注的高开销,能够捕捉微服务之间的依赖影响,从而在线识别性能不可预测的根因,并执行修正操作以恢复云服务的 QoS。

在专用本地集群以及 Google Compute Engine 上的大型集群中进行的实验表明,Sage 在准确识别 QoS 违约的根因方面,准确率始终超过 93%,同时还提升了系统性能的可预测性。