在微服务和分布式系统的复杂世界中,实现透明度并理解服务交互和请求流的复杂性和低效率已成为首要挑战。分布式跟踪对于理解分布式系统至关重要。但是,无论是手动应用还是自动检测,分布式跟踪通常都比较粗粒度。因此,分布式跟踪仅涵盖系统的一小部分,并且很容易遗漏最有用跟踪的系统部分。
为了解决这一差距,Elastic 开发了推断 span 的概念,作为对基于传统检测的跟踪的强大增强,作为 OpenTelemetry Java SDK/Agent 的扩展。我们正在将此贡献回 OpenTelemetry,在此之前,我们的 扩展 可以与现有的 OpenTelelemetry Java SDK 无缝使用(如下所述)。
推断 span 旨在增强基于检测的跟踪提供的可见性,揭示以前未检测的应用程序或库中的延迟源。此功能显著扩展了分布式跟踪的实用性,从而可以更全面地了解系统行为并促进对性能优化的更深入研究。
什么是推断 span?
推断 span 是一种可观察性技术,它将分布式跟踪与性能分析技术相结合,以照亮应用程序中较暗、未观察到的角落 — 标准检测技术无法触及的区域。推断 span 功能将从性能分析堆栈跟踪派生的信息与基于检测的跟踪数据交织在一起,从而可以根据从性能分析数据中提取的见解生成新的 span。
在处理对请求延迟做出重大贡献但缺乏内置或外部检测支持的自定义代码或第三方库时,此功能非常宝贵。通常,为这些片段识别或制作特定的检测可能从具有挑战性到完全不可行。此外,在某些情况下,由于潜在的巨大性能开销,实现检测是不切实际的。例如,检测应用程序锁定机制尽管它们的作用至关重要,但由于它们的普遍性以及检测可能会给应用程序请求带来的巨大延迟开销,因此是不可行的。不过,理想情况下,此类延迟问题应该在您的分布式跟踪中可见。
推断 span 可确保更深入地了解应用程序的性能动态,包括上述情况。
推断 span 的实际应用
为了演示推断 span 功能,我们将使用 Elastiflix 演示应用程序的 Java 实现。Elasticflix 有一个名为 favorites 的端点,它执行一些 Redis 调用,还包括一个人为延迟。首先,我们使用普通的 OpenTelemetry Java Agent 来检测我们的应用程序
java -javaagent:/path/to/otel-javaagent-<version>.jar \
-Dotel.service.name=my-service-name \
-Dotel.exporter.otlp.endpoint=https://<our-elastic-apm-endpoint> \
"-Dotel.exporter.otlp.headers=Authorization=Bearer SECRETTOKENHERE" \
-jar my-service-name.jar
通过 OpenTelemetry Java Agent,我们可以开箱即用地检测 HTTP 入口点和对 Elastiflix 应用程序的 Redis 调用。生成的跟踪包含 POST /favorites 入口点的 span,以及一些对 Redis 调用的短 span。
如您在上面的跟踪中看到的,不清楚大部分时间都花在 POST /favorites 请求的哪个地方。
让我们看看推断 span 如何阐明这些领域。您可以手动使用您的 OpenTelemetry SDK(请参阅下面的部分)来使用推断 span 功能,将其打包为上游 OpenTelemetry Java 代理的即插即用扩展,或者仅使用 Elastic 的 OpenTelemetry Java 代理发行版,它带有推断 span 功能。
为了方便起见,我们只需下载 Elastic 发行版的 代理 jar 并扩展配置以启用推断 span 功能
java -javaagent:/path/to/elastic-otel-javaagent-<version>.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:推断 span 功能目前是选择加入的,因此需要显式启用。运行启用推断 span 功能的同一应用程序会产生更全面的跟踪
推断 span(在上面的屏幕截图中以蓝色着色)遵循 Class#method 的命名模式。有了它,推断 span 功能可以帮助我们精确定位对请求的总体延迟贡献最大的确切方法。请注意,HTTP 入口 span、Redis span 和推断 span 之间的父子关系已正确重建,从而生成一个功能齐全的跟踪结构。
检查 Elastiflix 应用程序中的 handleDelay 方法会发现使用了简单的 sleep 语句。尽管 sleep 方法不是 CPU 绑定的,但此延迟的完整持续时间会作为推断 span 捕获。这源于使用 async-profiler 的挂钟时间分析,而不是仅依赖 CPU 分析。推断 span 功能能够反映实际延迟(包括 I/O 操作和其他非 CPU 绑定的任务)代表了一项重大进步。它允许诊断和解决超出 CPU 限制的性能问题,从而更细致地了解系统行为。
将推断 span 与您自己的 OpenTelemetry SDK 结合使用
OpenTelemetry 是一个高度可扩展的框架:Elastic 通过还将我们 OpenTelemetry Java 发行版附带的大部分扩展作为独立扩展发布到 OpenTelemetry Java SDK 来拥抱这种可扩展性。
因此,如果您不想使用我们的发行版(例如,因为您不需要或不希望在您的项目中使用字节码检测),您仍然可以使用我们的扩展,例如推断 span 功能的扩展。您需要做的就是在您的代码中设置 OpenTelemetry SDK 并将推断 span 扩展添加为依赖项
<dependency>
<groupId>co.elastic.otel</groupId>
<artifactId>inferred-spans</artifactId>
<version>{latest version}</version>
</dependency>
在您的 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://<your-elastic-apm-endpoint>")
.addHeader("Authorization", "Bearer <secrettoken>")
.build()).build())
.build();
inferredSpans.setTracerProvider(tracerProvider);
推断 span 扩展与 OpenTelemetry SDK 自动配置机制无缝集成。通过将 OpenTelemetry SDK 及其扩展作为依赖项包含在应用程序代码中,而不是通过外部代理,您可以灵活地使用相同的环境变量或 JVM 属性对其进行配置。将推断 span 扩展包含在您的类路径中后,为自动配置的 SDK 激活它将变得简单。只需使用 elastic.otel.inferred.spans.enabled 属性启用它(如前所述),即可通过最少的设置来利用此功能的全部功能。
推断 span 如何工作?
推断跨度功能利用了广泛使用的 async-profiler 的能力,该工具是一个低开销、流行的 Java 生态系统生产时间分析器,可以收集挂钟时间分析数据。然后,它将分析数据转换为可操作的跨度,作为分布式跟踪的一部分。但是,是什么机制允许这种转换呢?
本质上,推断跨度扩展与跨度事件的生命周期相关联,特别是在任何线程中通过 OpenTelemetry 上下文激活或停用跨度时。在事务中激活初始跨度时,扩展程序会通过 async-profiler 开始一个挂钟时间分析会话,该会话设置为预定的持续时间。同时,它会记录所有跨度激活和停用的详细信息,捕获它们各自的时间戳以及它们发生的线程。
在分析会话完成后,扩展程序会处理分析数据以及跨度事件的日志。通过关联数据,它会重建推断的跨度。重要的是要注意,在某些复杂的情况下,关联可能会为跨度分配不正确的名称。为了缓解这种情况并帮助进行准确识别,扩展程序会在 code.stacktrace 属性下使用堆栈跟踪段来丰富推断的跨度,从而为用户提供有关所涉及的确切方法的清晰度和洞察力。
推断跨度与跟踪与分析数据的关联
在 OpenTelemetry 最近发布了分析信号之后,加上Elastic 承诺将通用分析代理捐赠给 OpenTelemetry,您可能想知道推断跨度功能与仅使用跨度 ID 和跟踪 ID 将分析数据与分布式跟踪相关联有何不同。与其将这些视为相互竞争的功能,不如将它们视为互补的功能更为准确。
推断跨度功能和跟踪与分析数据的关联都采用类似的方法 — 将跟踪信息与分析数据融合。但是,它们各自在不同的领域中表现出色。推断跨度擅长识别可能因传统 CPU 分析而忽略的长时间运行的方法,而传统 CPU 分析更擅长找出 CPU 瓶颈。推断跨度的独特优势在于它能够考虑 I/O 时间,捕获诸如磁盘访问之类的操作引起的延迟,而这些延迟通常在 CPU 分析火焰图中不可见。
但是,推断跨度功能也有其局限性,尤其是在检测由“千刀万剐”引起的延迟问题时,即某个方法虽然每次调用并不耗时,但由于在整个请求中被调用多次而严重影响总延迟。虽然由于其短暂性,单个调用可能不会被捕获为推断跨度,但通过 CPU 分析可以揭示导致延迟的 CPU 绑定方法,因为火焰图会显示这些方法消耗的聚合 CPU 时间。
推断跨度功能的另一个优势在于其数据结构,它提供了一个简化的跟踪模型,概述了典型的父子关系、执行顺序和良好的延迟估计。此结构是通过将跟踪数据与跨度激活/停用事件和分析数据集成而实现的,从而有助于在各个跟踪中直接导航和排除延迟问题。
将分布式跟踪数据与分析数据相关联具有不同的优势。在我们的相关博客文章中了解更多信息,超越跟踪:使用连续分析和分布式跟踪关联来精确定位性能罪魁祸首。
性能开销如何?
如前所述,推断跨度功能基于广泛使用的 async-profiler,该工具以其对性能的最小影响而闻名。但是,分析操作的效率并非没有其注意事项,这在很大程度上受到所采用的特定配置的影响。在这种平衡中,一个关键因素是采样间隔 — 样本之间的间隔越长,产生的开销越低,但代价是可能会忽略对推断跨度功能发现过程至关重要的较短方法。
调整基于概率的跟踪采样提供了另一种优化方法,直接影响开销。例如,将跟踪采样设置为 50% 有效地将分析负载减半,从而使推断跨度功能在平均每次请求时更加节省资源。这种细致入微的调整方法可确保推断跨度功能可以在实际生产环境中使用,并且具有可管理的性能占用空间。如果配置正确,此功能可提供一种强大的低开销解决方案,用于增强生产应用程序中的可观察性和诊断功能。
推断跨度和 OpenTelemetry 的未来是什么?
这篇博客文章概述并介绍了推断跨度功能,该功能可作为 OpenTelemetry Java SDK 的扩展使用,并内置于新推出的 Elastic OpenTelemetry Java Distro 中。推断跨度允许用户在使用传统跟踪数据的同时,对未显式检测的代码区域中的延迟问题进行故障排除。
该功能目前只是现有专有 Elastic APM 代理中功能的移植。随着 Elastic 拥抱 OpenTelemetry,我们计划将此扩展贡献给上游 OpenTelemetry 项目。为此,我们也计划将扩展迁移到最新的 async-profiler 3.x 版本。亲自试用推断跨度,看看它如何帮助您诊断应用程序中的性能问题。
本文中描述的任何功能或特性的发布和时间安排均由 Elastic 自行决定。任何当前不可用的功能或特性可能不会按时交付或根本不交付。