**微服务系统是高度动态和复杂的。对于这样的系统,运营工程师和开发人员高度依赖 trace 分析来理解架构和诊断各种问题,如服务失败和质量下降。然而,在运行时产生的大量痕迹使得实时捕获所需信息成为挑战。为了解决面临的挑战,在本文中,我们提出了一种基于图的微服务 trace 分析方法,名为 GMTA,用于理解架构和诊断各种问题。GMTA 建立在一个基于图的表示上,包括有效地处理飞行中产生的 trace 。它将痕迹抽象为不同的路径,并将它们分组为业务流。为了支持各种分析应用,GMTA 包括一个有效的存储和访问机制,它结合了一个图数据库和一个实时分析数据库,并使用一个精心设计的存储结构。基于 GMTA,我们构建了用于架构理解和问题诊断的分析应用;这些应用支持各种需求,如可视化服务依赖性、做出架构决策、分析服务行为的变化、检测性能问题和定位根本原因。GMTA 已经在 eBay 中实施和部署。一项基于 eBay 产生的 trace 数据的实验研究证明了 GMTA 对于架构理解和问题诊断的有效性和效率。在 eBay 的监控团队和网站可靠性工程(SRE)团队进行的一项案例研究进一步证实了 GMTA 在工业规模的微服务系统中的巨大优势。
1 序言
微服务架构具有交付速度快、可扩展性强、自主性强等优点,因此成为构建云原生应用的最新趋势。微服务系统被实现为一套小型服务,每个服务都在其进程中运行,并通过轻量级机制(如 HTTP 资源 API)进行通信[15]。微服务系统是高度复杂和动态的。在微服务系统中,每个请求可能导致一系列同步或异步执行的分布式服务调用。一个服务可以有几千到几万个实例,由微服务发现服务(例如 Docker swarm 的服务发现组件)动态地创建、销毁和管理[21, 22]。
对于微服务系统来说,运营工程师和开发人员高度依赖 trace 分析来理解架构和发现各种问题。由于微服务系统的高度复杂性和动态性,其运营工程师和开发人员很难使用静态分析和日志来实现这些目的。此外,微服务系统要经历频繁的部署,这将不断改变其服务的依赖关系和行为。因此,工业微服务系统通常配备了分布式 trace 系统,该系统 trace 跨服务实例的请求执行情况。分布式 trace 系统将服务操作的每一次调用记录为一个 span ,将每个外部请求的执行过程记录为一个 trace ,同时记录相关属性,如延迟和错误状态。通过分析 trace 数据(包括 span 和 trace ),操作工程师和开发人员可以了解服务之间的互动和延迟,并准确指出故障发生的地方和导致性能不佳的原因[18]。
然而,运行时产生的大量痕迹使得实时捕捉所需信息成为挑战,其中有两个特别的挑战:(1)需要对痕迹数据进行有效处理,以产生不同层次和高质量的聚合痕迹表示;(2)特定痕迹的详细信息可以按需提供。例如,在 eBay,微服务系统每天产生近 1500 亿条痕迹。因此,对于架构的理解,有必要汇总这些痕迹,以展示大量服务的依赖性和行为,同时揭示部署和更新引起的变化。对于问题的诊断,需要快速识别异常的痕迹,并且可以按需提供它们的细节(例如,相关指标,如响应时间和错误率)。
为了应对这些挑战,在本文中,我们提出了一种基于图形的 trace 分析方法,名为 GMTA,用于理解微服务架构和诊断各种问题。GMTA 建立在一个基于图的表示上,包括有效地处理飞行中产生的痕迹。它将痕迹抽象为不同的路径,并进一步将它们归入业务流。为了支持各种分析应用,GMTA 包括一个高效的存储和访问机制,它结合了一个图数据库和一个实时分析数据库,并使用一个精心设计的存储结构。在 GMTA 的基础上,我们构建了 GMTA Explorer,用于架构理解和问题诊断,支持各种需求,如可视化服务依赖性,做出架构决策,分析服务行为的变化,检测性能问题,并定位根本原因。
GMTA 已经在 eBay 实施和部署。为了评估 GMTA 的有效性和效率,我们对 eBay 的真实 trace 数据进行了实验研究,包括 1978.9 亿个 span 和 102.9 亿条 trace 。该研究将 GMTA 与两种传统的 trace 处理方法进行了定性和定量的比较。我们推导出六种 trace 分析场景,需要不同层次的 trace 数据(即 trace 、路径、业务流)。结果表明,GMTA 可以有效地支持所有这些场景,其有效性大大超过了两种传统方法。我们向 eBay 的监控团队和网站可靠性工程(SRE)团队展示了基于 GMTA 的资源管理器,并在实际任务中进行了案例研究。研究结果进一步证实了 GMTA 在工业规模的微服务系统中的巨大优势。
2 背景和动机
在微服务的趋势下,一个现代的分布式系统一般会涉及数百或数千个微服务。这些微服务的复杂调用关系使得系统的开发、管理和监控变得困难。传统的以机器为中心的监控运行机制并不有效,因为缺乏对分布式服务的节点和依赖关系所做工作的一致看法。为了解决这个问题,近年来提出了基于以工作流为中心的 trace 技术的端到端 trace [5, 8, 18]。 trace 的基本概念很简单:在分布式服务的代码中选定的点上的仪器在执行时产生数据,来自一个给定请求的不同执行点的数据可以被组合起来产生一个整体的 trace 。例如,对于一个基于请求的分布式服务,每个 trace 将显示服务组件内部和之间为处理一个请求所做的工作。由于端到端 trace 捕获了分布式系统内部和组件之间的因 果相关活动的详细工作,因此有越来越多的行业实现,包括谷歌 的 Dapper [18]、云德拉的 HTrace [5]、推特的 Zipkin [8]等。 展望未来,端到端 trace 有可能成为提供云环境中数据中心内部和 数据中心间活动的全局视图的基本基础。
然而,利用 trace 数据来监视和理解架构面临着两个主要挑战。 首先,有效地处理、存储和分析巨大的实时 trace 数据具有挑战性 ,因为随着微服务系统规模的增加, trace 数据的数量急剧增加。 其次, 由于大型服务生态系统可以由各种应用程序框架和不同的 系统组成,因此使用 trace 数据构建有效的应用程序需要解决 trace 数据的质量问题,如错误链和断链。
为了解决前两个挑战,我们建议使用 GMTA 和 GMTA 资源管理器来 支持微服务监控和故障排除。在基本的分布式 trace 实现到位后, 利用大量的分布式 trace 数据进行洞察是有价值的。特别是对于开 发人员和 SREs 来说,观察应用程序的行为并分析它们可以获得更 多的知识来进行故障排除,甚至进行业务分析。2019 年,整个 eBay 服务生态系统有数百万行代码和配置变化。仅基于设计文档 或领域知识,几乎不可能捕获或理解这些变化。
3 个基于图形的微服务 trace 分析 (GMTA) 系统
图 1 展示了基于图形的微服务 trace 分析 (GMTA) 系统的概述。它 包括三个模块:处理、存储和访问。处理模块将原始数据作为输 入,即从分布式 trace 系统获取的 span 日志,并将 span 组合为 trace 、路径,直到业务流。这些处理结果被保存在存储器中, 以供进 一步分析。为了支持灵活和高效的 trace 数据访问,该存储模块结 合了图形数据库和实时分析数据库,并使用了一个精心设计的存 储结构。访问模块在三个级别上提供了灵活和高效的 trace 数据访 问接口:业务流级别、路径级别和 trace 级别。
trace 数据是由大量的服务实例连续生成的一种流式数据。该系 统的总体设计原则是灵活地结合基于图和非图的 trace 数据处理和 存储,以高效地访问大量不同级别的 trace 数据。

图 1:GMTA 系统概述

图 2:基于图形的 trace 数据表示
在本节的其余部分中,我们首先介绍不同级别的 trace 数据的基于图的表示,然后详细介绍这三个模块 (处理、访问和存储) 。
3.1 基于图的表示
我们基于图形的微服务 trace 分析系统建立在一系列关于 trace 和流程分析的概念之上。这些概念和它们的关系在图 2 所示的概念模型中得到了描述。
一个服务提供一组操作,每个操作的调用是一个 span 。每个 span 有一个唯一的 ID 和一组属性,如被调用的服务和操作,开始时间和持续时间以及父 span 的 ID。 trace 代表一个外部请求的执行过程。每个 trace 都有一个唯一的 ID 和一个由 span 组成的树状结构;树状结构中的父子关系代表服务调用关系。具有完全相同的树状结构(即相同的服务操作和调用顺序)的 trace 可以被抽象为一个路径。因此,一个 trace 可以被理解为一个路径的实例。一个路径有一个由跳数组成的树状结构;每个跳数都是由 trace 中的相应 span 抽象出来的。实现同一场景的路径可以进一步归纳为一个业务流。因此,一个 trace 类型可以被看作是一个业务流的变体,一个业务流的路径通常可以根据涉及的一些关键服务操作来选择。通常,一个业务流可以指定其路径必须经过的关键操作,或者进一步指定这些操作的执行顺序。

图 3:业务流程的一个例子(下订单)
图 3 显示了一个下订单的示例业务流,它指定 "创建订单 "为其关键操作。在图中,一个椭圆代表一个服务,一个矩形代表一个操作,一个粗箭头代表一个跳跃,一个细箭头代表一个 span 。业务流包括两条路径,即客人结账和用户结账,它们分别由一系列红色和绿色的跳转组成。每条路径都有一些痕迹作为其实例。例如,客人结账路径有两个轨迹,分别由一系列蓝色和黑色的 span 组成。请注意,由于篇幅限制,它们中的一些 span 没有被描绘出来。
业务流的两条路径根据确定的关键操作(这里是 "创建订单")被归类在一起。它们都有一个树状结构,只在结账操作("guestCheckout "或 "userCheckout")上有所不同。客人结账的路径有两条痕迹。它们具有完全相同的树状结构,记录了路径的两个执行实例。例如,"guestCheckout "和 "createOrder "之间的两个 span 具有不同的 trace ID、 span ID、时间戳和持续时间。
为了支持错误传播分析,我们还定义了错误传播链(简称 EP 链)的概念,它是一个传播错误的 span 序列。例如,EP 链 "coupon → calculateMoney → createOrder "表示一个错误从 "coupon "传播到 "createOrder"。
3.2 数据处理
trace 的 span 日志是由参与 trace 的服务实例以分布式方式产生的。GMTA 的处理模块将这些 span 集合成一个 trace ,并进一步清理、聚合和分析 trace 。所有前面的处理都是以流的方式进行的,这可以基于流处理框架来实现,比如 Flink[2]。
3.2. 1 trace 的组装和修复。为了组装一个 trace ,我们需要收集所有的 span ,并根据每个 span 中记录的父 span ID 连接它们。 trace 组装是一个持续的过程,在此期间,来自不同服务实例的大量 span 以流式方式产生和接收。为了有效地处理流数据,我们采用了时间窗口策略。我们收集并分组具有相同 trace ID 的 span ,并且可以在给定的时间窗口(例如 5 分钟)内达到,这是基于一个假设,即一个 trace 的所有 span 可以在短时间内产生和接收。然后对于每一组 span ,我们尝试在内存中组装一个 trace 。
span 日志是由大量的服务产生的,这些服务是由不同的开发者开发的,并通过复杂的分布式网络收集。因此,一些 span 日志可能包括不正确或不完整的信息,使得出的痕迹无效。目前,GMTA 检测并修复了两种这样的问题:无效的操作名称和破碎的 trace 。
无效的操作名称通常是由日志中错误传递的参数引起的,例如,请求中包含的用户 ID 可能被错误地传递为操作名称的一部分。一个服务的操作数量通常是稳定的,而且不大。因此,我们可以通过监测同一服务的不同操作名称的数量变化来识别 span 日志中的无效操作名称。我们提示用户检查识别出的无效操作名称,并要求用户提供匹配和替换规则,例如使用正则表达式。然后,后来到达的无效操作名称可以被自动匹配并替换为正确的名称。通过这种方式,可以避免由无效的操作名称引起的错误路径。
断裂的轨迹通常是由于开发规范和历史遗留系统造成的日志中数据传递不完整,例如,父 span ID 在 span 中丢失。基于集合 trace 的树状结构,我们检测并修复三种断裂的 trace 。
(1) 一个 trace 没有根。
(2) 一个 trace 有一个以上的根。
(3) 一个 trace 有一个根,但是一些 span 没有父 span 。
对于第一种情况,我们只需添加一个根节点,并将其作为没有父 span 的 span 的父节点。对于第二种和第三种情况,我们试图通过时间戳匹配来修复断裂的 trace 。给定一个没有父 span 的 span 或子树 S 的根,我们试图以下列方式为它找到一个父 span :如果在其他子树中有一个叶 span P,满足 S 的开始时间和结束时间在 P 的持续时间内的条件,P 被视为 S 的父 span ,两个子树因此被连接。
在 trace 组装过程中,我们还根据 span 的错误标签来识别 EP 链。给定一个有错误标签的 span ,我们检查该 span 的一个子 span 是否也有一个错误标签。如果没有,当前 span 被认为是 EP 链的起点,并且一个特殊的标签和链的长度被设置在 span 中。
3.2.2 路径识别。路径识别是与 trace 组装一起连续进行的。与 trace 不同的是,路径的数量是相对稳定的。通常只有在系统更新或服务调用中出现错误/异常时才会出现新的路径。路径识别需要检查两个 traces 是否有相同的树状结构。为了避免 trace 之间昂贵的树形比较,我们为每个 trace 生成一个路径 ID,可以唯一地识别一个路径。路径 ID 是通过使用算法 1 计算根 span 的 hash code 而产生的。给定一个 span ,该算法分别为其服务名称和操作名称计算一个哈希代码,然后将这两个哈希代码和根据该 span 在 trace 树中的级别计算的偏移量相加。如果该 span 有子 span ,该算法将递归地计算每个子 span 的 hash code,并将它们加在一起。

给定一个轨迹,我们为它计算一个路径 ID,并检查是否已经存在相同 ID 的路径。如果路径存在,我们就更新它的属性,如 trace 计数、平均延迟和不同 EP 链的发生时间。如果该路径不存在,我们就为该 trace 创建一个新的路径。例如,图 3 中红色箭头内的两个 trace 得到了相同的路径 ID,因为所传递的操作和序列完全相同。当蓝色箭头所代表的轨迹出现时,因为这个路径 ID 是第一次出现,我们将当前轨迹所传递的操作和相关信息记录为一个新的路径。但是当紫色箭头代表的轨迹出现时,这个路径 ID 已经存在,所以我们根据当前的轨迹属性更新相应的路径信息。

图 3:业务流程的一个例子(下订单)
3.2.3 业务流识别。业务流可以由开发人员和操作工程师以按需方式定义。例如,除了下订单的业务流(见图 3),开发者可以定义一个更新库存的业务流,以检查所有涉及调用 "updateInventory "操作的路径。通过分析这个业务流,开发者可以找到关于库存更新的故障线索。
业务流可以被定义为以下两种类型的任何数量的基本条件的逻辑组合(使用 AND/OR 操作)。
(1) 一个服务操作被调用。
(2) 一项服务操作在另一项操作之前或之后被调用。
业务流的识别包括检查
特定服务调用的存在以及路径中特定服务调用的顺序。这种检查可以由图数据库(如 Neo4j[7])有效地支持。因此,我们在存储模块的图数据库中持续存储和更新已识别的路径,并使用图查询来动态地识别符合特定业务流定义的路径。
3.3 数据访问
GMTA 的目标是为各种 trace 分析应用 提供有效和灵活的访问支持,如架构理解和问题诊断。 trace 数据的访问要求可以分为以下三个层次。
[图片上传失败...(image-40f38e-1677461538640)]
(1) 跟踪级别的访问。跟踪级别的访问包括跟踪搜索和跟踪细节查询。追踪搜索可以搜索所有涉及特定服务操作的调用或发生在特定时间范围内的追踪。追踪细节查询请求给定追踪的各种类型的信息(通常是通过追踪ID),包括其跨度和相关指标,如持续时间。该查询还可以请求一个跟踪的扩展信息,如其路径ID和涉及的EP链。
(2) 路径级访问。路径级访问包括路径搜索和路径细节查询。路径搜索可以搜索所有涉及特定服务操作调用的路径。路径细节查询请求给定路径的各种类型的信息(通常按路径ID),包括其在给定时间范围内的跟踪,跟踪计数,以及其他综合指标,如错误率和平均延时。查询还可以要求提供路径的扩展信息,如涉及的EP链和发生时间。
(3) 业务流级别的访问。业务流层面的访问 包括业务流搜索和业务流细节查询。业务流搜索可以搜索所有涉及特定路径的业务流。涉及特定的路径。业务流细节查询请求某业务流的路径 业务流的路径(通常是通过业务流名称)在一个给定的 时间范围。
对于前面的每一个要求,都可以定义和实现一个基本的访问接口,以支持对特殊的跟踪数据的有效访问。除了这些基本的接口,其他更具体的访问接口也可以在我们基于图的跟踪数据表示法的基础上定义和实现。尽管一些跟踪数据的访问需求也可以通过组合基本的访问接口来满足,但为它们设计特定的接口可能更有效。例如,一个跟踪分析需求可能是找到一个服务直接或间接调用另一个服务的n个跳数的所有路径。这个要求可以通过使用基本的访问接口来实现:我们首先获得包括两个服务的所有路径,然后过滤掉两个服务之间的调用超过n跳的路径。如果我们为这个要求设计一个特定的接口,那么跟踪数据的访问就会更有效率,因为满足条件的路径可以直接通过图查询获得。
跟踪数据的处理和分析涉及到基于图形的查询,例如搜索涉及特定服务调用的路径。这种操作可以由Neo4j[7]等图数据库有效支持。同时,跟踪数据的处理和分析也涉及对非基于图的数据的实时分析,如选择过去一小时内特定服务操作的跨度并按路径ID分组。这类操作可以由实时分析的OLAP(在线分析处理)数据库有效支持,如Druid[1]和Clickhouse[4]。因此,存储模块的一个关键设计决定是如何在这两种数据库之间分配跟踪数据。
同一路径的所有跟踪都具有完全相同的结构,而且路径比跟踪要稳定得多。这些特点意味着痕迹级的图查询(例如,找到所有涉及特定服务调用的痕迹)可以通过相应的路径级图查询来实现。基于这一观察,我们设计了一个混合存储结构,如图4所示。该图显示了图数据库的图结构和分析数据库的主要表格。该图还显示了图数据库中节点和边的主要属性,以及它们对分析数据库中字段的引用。

图 4:数据存储结构

图 2:基于图形的 trace 数据表示 我们将基于图的表示法的相对稳定部分(见图2)存储在图数据库中,包括服务、操作、跳数和路径。两个服务操作之间可以有多条边,每条边代表一个特定路径的一个跳数。因此,一个路径的跳数可以通过使用路径ID来过滤图形的图形查询获得。我们在分析数据库中存储跟踪跨度的详细信息,如跨度ID、跟踪ID、服务名称、操作名称、时间戳、持续时间和错误标签。分析数据库应支持实时查询一定时期内的全部数据范围,或根据指定字段进行自动汇总。每个跨度记录其路径ID,以便能够选择特定路径的跨度和痕迹。由于一个业务流代表一组路径,我们将业务流和路径之间的映射关系存储在分析数据库中。
所有前面的数据都在追踪数据处理中不断更新。当一个跟踪被集合起来时,它的跨度被添加到跨度表中,其路径被分析。如果一个新的路径被创建,它的跳数和可能的新服务和操作被添加到图数据库中。通过查询图数据库中的路径,定期进行业务流识别,并将识别的业务流更新到分析数据库的业务流映射表中。为了支持高效的数据查询,我们在图数据库中增加了一些综合指标,如一个跳的跟踪数和错误数作为跳的属性。
我们目前对GMTA的实现使用Neo4j[7]作为图数据库,使用Druid[1]作为分析数据库。
表1:GMTA资源管理器的功能

网友评论