使用 OpenTelemetry 中的推断跨度来揭示跟踪数据中的未知信息
2024年5月3日 | by mebius
在微服务和分布式系统的复杂世界中,实现透明度并了解服务交互和请求流程的复杂性和低效性已成为一个重要挑战。分布式跟踪对于理解分布式系统至关重要。但是,无论是手动应用还是自动检测,分布式跟踪通常都相对粗粒度。因此,分布式跟踪仅覆盖系统的有限部分,往往会错过系统中最有价值的跟踪部分。
为了解决这一差距,Elastic 开发了推断跨度(inferred spans)的概念,作为基于传统检测(traditional instrumentation-based)的跟踪的强大增强,作为 OpenTelemetry Java SDK/Agent 的扩展。我们正在将这一概念贡献回 OpenTelemetry,但在此之前,我们的扩展可以无缝地与现有的 OpenTelemetry Java SDK 配合使用(如下所述)。
推断跨度旨在增强基于检测跟踪提供的可见性,揭示先前未经检测的应用程序或库中的延迟源。这个特性显著扩展了分布式跟踪的实用性,允许更全面地理解系统行为,并促进更深入地进行性能优化。
推断跨度是什么?
推断跨度是一种可观察性技术,它将分布式跟踪与性能分析技术相结合,用于照亮应用程序中较暗、未被观察到的角落 —— 这些区域是标准检测技术难以覆盖的地方。推断跨度功能将来自性能分析堆栈跟踪(profiling stracktraces)的信息与基于检测(instrumentation-based)的跟踪数据交织在一起,允许基于从性能分析数据中提取的洞察生成新的跨度。
当处理对请求延迟有显著贡献但缺乏内置或外部检测支持的自定义代码或第三方库时,这个功能证明是非常宝贵的。通常,识别或制定这些部分的特定检测可以从具有挑战性到完全不可行不等。此外,存在某些情况,由于潜在的显著性能开销,实施检测是不切实际的。例如,尽管应用程序锁定机制的角色至关重要,但由于其普遍性和检测可能给应用程序请求带来的显著延迟开销,对其进行仪器化是不可行的。然而,在理想情况下,此类延迟问题应在分布式跟踪中可见。
推断跨度确保了对你的应用程序性能动态的更深入可见性,包括上述情况。
推断跨度的实际运用
为了演示推断跨度功能,我们将使用 Elastiflix 演示应用程序的 Java 实现。Elasticflix 有一个名为 favorites 的端点,其中执行一些 Redis 调用,并包含人为延迟。首先,我们使用纯 OpenTelemetry Java 代理来检测我们的应用程序:
java -javaagent:/path/to/otel-javaagent-.jar
-Dotel.service.name=my-service-name
-Dotel.exporter.otlp.endpoint=https://
"-Dotel.exporter.otlp.headers=Authorization=Bearer SECRETTOKENHERE"
-jar my-service-name.jar
使用 OpenTelemetry Java 代理,我们为 Elastiflix 应用程序的 HTTP 入口点和对 Redis 的调用获得即插即用的检测。生成的跟踪包含用于 POST /favorites 入口点的跨度,以及对 Redis 的几个短跨度。
正如你在上面的跟踪中所看到的,POST /favorites 请求中大部分时间都花费在哪里并不清楚。
让我们看看推断跨度如何为这些区域提供启示。你可以使用推断跨度功能,要么手动使用你的 OpenTelemetry SDK(请参阅下面的部分),要么将其打包为上游 OpenTelemetry Java 代理的即插即用扩展,或者只使用 Elastic 提供的 OpenTelemetry Java 代理发行版,该发行版附带了推断跨度功能。
为方便起见,我们只需下载 Elastic 发行版的 agent jar 包,并扩展配置以启用推断跨度功能:
java -javaagent:/path/to/elastic-otel-javaagent-.jar
-Dotel.service.name=my-service-name
-Dotel.exporter.otlp.endpoint=https://XX.apm.europe-west3.gcp.cloud.es.io:443
"-Dotel.exporter.otlp.headers=Authorization=Bearer SECRETTOKENHERE"
-Delastic.otel.inferred.spans.enabled=true
-jar my-service-name.jar
这里唯一的非标准选项是 elastic.otel.inferred.spans.enabled
:推断跨度功能目前需要选择加入,因此需要显式启用。使用启用了推断跨度功能的同一应用程序运行,可以产生更全面的跟踪:
推断跨度(在上述截图中以蓝色显示)遵循命名模式 Class#method。通过这种方式,推断跨度功能帮助我们准确定位对请求的整体延迟做出最大贡献的确切方法。请注意,HTTP 入口跨度、Redis 跨度和推断跨度之间的父子关系被正确重建,从而产生了一个完全功能的跟踪结构。
检查 Elastiflix 应用程序中的 handleDelay 方法会显示使用了一个简单的 sleep 语句。尽管 sleep 方法不是 CPU 绑定的,但这个延迟的全部持续时间被捕获为推断跨度。这是由于使用了 async-profiler 的墙时钟时间分析,而不仅仅依赖于 CPU 分析。推断跨度功能能够反映实际的延迟,包括 I/O 操作和其他非 CPU 绑定的任务,这代表了一个重大进步。它允许诊断和解决超出 CPU 限制的性能问题,提供了对系统行为更细致入微的视角。
使用你自己的 OpenTelemetry SDK 来进行推断跨度
OpenTelemetry 是一个高度可扩展的框架:Elastic 充分利用了这种可扩展性,通过将我们的 OpenTelemetry Java 发行版中的大多数扩展发布为 OpenTelemetry Java SDK 的独立扩展。
因此,如果你不想使用我们的发行版(例如,因为你的项目不需要或不想要字节检测),你仍然可以使用我们的扩展,例如推断跨度功能的扩展。你只需在你的代码中设置 OpenTelemetry SDK,并将推断跨度扩展添加为依赖项即可:
co.elastic.otel
inferred-spans
{latest version}
在 SDK 设置期间,你必须初始化并注册扩展:
InferredSpansProcessor inferredSpans = InferredSpansProcessor.builder()
.samplingInterval(Duration.ofMillis(10)) //the builder offers all config options
.build();
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(inferredSpans)
.addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder()
.setEndpoint("https://")
.addHeader("Authorization", "Bearer ")
.build()))
.build();
inferredSpans.setTracerProvider(tracerProvider);
推断跨度扩展与 OpenTelemetry SDK 自动配置机制无缝集成。通过在你的应用程序代码中作为依赖项引入 OpenTelemetry SDK 及其扩展 —— 而不是通过外部代理 —— 你可以获得使用相同的环境变量或 JVM 属性来配置它们的灵活性。一旦推断跨度扩展包含在你的类路径中,为自动配置的 SDK 激活它就变得简单直接。只需使用 elastic.otel.inferred.spans.enabled
属性启用它,如前所述,即可利用此功能的全部能力,同时最小化设置工作。
推断跨度如何工作?
推断跨度功能利用广泛使用的 async-profiler 收集墙时钟(wall clock)时间的性能分析数据,async-profiler 是 Java 生态系统中低开销且受欢迎的生产时分析器。然后,它将性能分析数据转换为可操作的跨度,作为分布式跟踪的一部分。但是,是什么机制实现了这种转换呢?
基本上,推断跨度扩展与跨度事件的生命周期相关联,特别是当跨度通过 OpenTelemetry 上下文在任何线程中被激活或停用时。在 transaction 中激活初始跨度时,扩展会通过 async-profiler 开始一段墙时钟(wall-clock)性能分析会话,设置为预定的持续时间。同时,它记录所有跨度激活和停用的细节,捕获它们各自的时间戳以及发生它们的线程。
在完成性能分析会话后,扩展会将性能分析数据与跨度事件日志一起处理。通过相关性数据,它重构推断跨度。需要注意的是,在某些复杂情况下,相关性可能会为跨度分配一个错误的名称。为了缓解这一点并帮助准确识别,扩展会使用代码堆栈跟踪片段丰富推断跨度,将其存储在 code.stacktrace 属性下,为用户提供了对涉及的具体方法的清晰见解。
推断跨度与将跟踪数据与性能分析数据相关联相比
随着 OpenTelemetry 最近宣布推出性能分析信号,并结合 Elastic 承诺将通用性能分析代理捐赠给 OpenTelemetry,你可能想知道推断跨度功能与仅仅使用跨度 ID 和跟踪 ID 相关联的性能分析数据有何区别。与其将它们视为竞争性功能,不如将它们视为互补的功能更为准确。
推断跨度功能和跟踪与性能分析数据的相关性都采用了类似的方法论 —— 将跟踪信息与性能分析数据融合在一起。然而,它们各自在不同领域表现出色。推断跨度在识别长时间运行的方法方面表现出色,这些方法在传统的 CPU 性能分析中可能被忽略,传统的 CPU 性能分析更擅长于精确定位 CPU 瓶颈。推断跨度的一个独特优势是它能够考虑 I/O 时间,捕获由磁盘访问等操作引起的延迟,这些操作通常在 CPU 性能分析火焰图中不可见。
然而,推断跨度功能也有其局限性,特别是在检测由于 “千刀万剐 – death bytgcode a thousand cuts” 而导致的延迟问题方面 —— 即使一个方法在每次调用时都不耗时,但由于在请求中多次调用,却会显著影响总延迟。虽然由于其短暂性而可能无法捕获个别调用作为推断跨度,但通过 CPU 性能分析,火焰图显示了这些方法消耗的累积 CPU 时间,从而揭示了导致延迟的 CPU 绑定方法。
推断跨度功能的另一个优势在于其数据结构,提供了一个简化的跟踪模型,概述了典型的父子关系、执行顺序和良好的延迟估计。通过将跟踪数据与跨度激活/停用事件和性能分析数据集成,实现了这种结构,有助于在单个跟踪中进行简单的导航和故障排除以解决延迟问题。
将分布式跟踪数据与性能分析数据相关联具有不同的优势。你可以在我们的相关博客文章中了解更多信息,标题为《超越跟踪:通过持续性能分析和分布式跟踪相关性准确定tgcode位性能问题罪魁祸首》。
性能开销如何?
如前所述,推断跨度功能基于广泛使用的 async-profiler,以其对性能影响最小而闻名。然而,性能分析操作的效率并非没有局限性,很大程度上受到所采用的特定配置的影响。在这种权衡中,一个关键因素是采样间隔—— 采样间隔越长,产生的开销越低,尽管可能会忽视潜在关键的较短方法,这些方法对于推断跨度功能的发现过程至关重要。
调整基于概率的跟踪采样是另一种优化方式,直接影响性能开销。例如,将跟踪采样设置为 50% 有效地减半了性能分析负载,使得推断跨度功能在平均每个请求上的资源利用率进一步提高。这种微妙的调整方法确保了推断跨度功能可以在真实的生产环境中使用,具有可管理的性能印记。当正确配置时,这个功能为增强生产应用程序的可观察性和诊断能力提供了一种强大的、低开销的解决方案。
下一步是推断跨度和 OpenTelemetry ?
本博客文章介绍了推断跨度功能,作为 OpenTelemetry tgcodeJava SDK 的扩展,并内置于新引入的 Elastic OpenTelemetry Java 发行版中。推断跨度允许用户在未显式检测的代码区域中使用传统跟踪数据来排查延迟问题。
该功能目前仅是从专有的 Elastic APM agent 移植而来。随着 Elastic 支持 OpenTelemetry,我们计划将这个扩展贡献给上游的 OpenTelemetry 项目。为此,我们还计划将该扩展迁移到最新的 async-profiler 3.x 发布版本。尝试推断跨度,看看它如何帮助你诊断应用程序中的性能问题。
本文中描述的任何功能或功能的发布和时间安排均完全由 Elastic 自行决定。本文中目前不可用的任何功能或功能可能无法按时或完全交付。
原文:Revealing unknowns in your tracing data with inferred spans in OpenTelemetry | Elastic Blog
文章来源于互联网:使用 OpenTelemetry 中的推断跨度来揭示跟踪数据中的未知信息