Java 21和Spring Boot 3.2增强监控可观察性指南


本文介绍了Spring Boot 3.2和Java 21中增强的可观察性支持。

通过利用Java 21和Spring Boot 3.2的高级功能,开发人员可以增强系统的可观察性,有效监控各种指标和日志。

Java 21还引入了虚拟线程和结构化并发,从而使可观察性更加简单清晰。

可观察性是通过观察系统的外部特征/输出来监视系统的能力。在软件系统上,可观察性用于确定运行系统如何满足请求、响应事件、接口/模块之间的交互方式、处理大量用户请求时的响应能力、不同负载下的吞吐量等。

可观察性跟踪代码的性能、响应性、稳定性等,同时可以在同一时间跟踪代码在所有位置或运行模块中的运行情况,从而提供代码性能的整体视图。

可观察性的好处包括清晰显示出现的性能模式、进程所需的时间、繁忙或空闲的端点以及并行运行的查询/客户端等。

所有这些性能数据都被聚合并以可视化方式呈现,以便轻松分析模式和趋势。这些信息可以帮助发现潜在的瓶颈。

要点:

  • Spring Boot3.2和Java 21增强了可观测性支持,开发人员可以通过监控指标和日志来提高系统的可观测性。
  • 可观测性通过追踪请求在软件系统中的行为和响应来提供对系统运行的可见性,以便分析性能、稳定性和其他指标。
  • Micrometer是一种流行的度量仪器库,可以记录和处理跟踪,提供有用的指标和可视化,而Java 21的虚拟线程和结构化并发使可观测性更加简单明了。

Micrometer
Micrometer 是一个度量仪器库,可在系统响应请求/事件并进一步处理它们时记录/跟踪跟踪。然后,Micrometer 可以将这些痕迹提供给跟踪系统,进行聚合和处理,以提供有用的指标和可视化。Micrometer 传统上支持许多跟踪系统,如 Jaeger、Zipkin、Prometheus 和 Datadog 等。

Micrometer 是一个非常流行的工具,它具有最小的代码占用量,并且它试图避免 java 反射,因此它可以使用最少的系统资源与您的应用程序一起运行。

在 Spring Boot 3 之前,程序员需要使用  Micrometer(外部依赖)来获取指标,使用 spring-cloud sleuth来进行分布式跟踪(依赖于 Micrometer 的外部库)。

Spring Boot 3.2 中使用 Java 21 增强了可观察性支持
从 Spring Boot 3 开始,无需配置和使用外部库即可实现可观察性。

只需要使用注解即可。您只需在类路径(spring-boot-starter-aop)中添加 Spring Boot AOP 库,然后使用
Micrometer 现有的注释,如 <span class="pink"> @Observed、@Counted、@Timed、@TimedSet、@NewSpan 和 @ContinueSpan等。

这些注释的方面现在是自动的注入

@Service
public class MyObservableService {

@Observed(name = "tracedMethod", lowCardinalityKeyValues = {"locale", "en-US"})
public void tracedMethod() {

// 在此编写业务逻辑,Spring 将在内部创建必要的观察对象。

}

}

Spring Boot 3.2 本质上会为您进行观察。Spring 3.2 有一个名为spring.reactor.context-propagation的新配置属性,如果将其设置为 auto,它会自动在反应式管道中传播观察、跟踪 ID 和跨度 ID 

  • 此外,Spring Boot 3.2 现在内置了对反应式关系数据库连接或R2DBC的支持,要启用它,请将io.r2dbc:r2dbc-proxy依赖项添加到您的项目中。
  • 如果您使用 Kafka,则可以设置 spring.kafka.template.observation-enabled 属性来支持 Micrometer 观察。
  • 最后,您还可以通过设置以下属性来禁用 Micrometer:  management.metrics.export.enabled=false


对 OpenTelemetry 支持
Spring Boot 的执行器模块包括对OpenTelemetry的基本支持。

但是您必须自己配置 OpenTelemetry,因为没有提供自动配置,但这并不困难。

Spring Boot 提供了两种主要的 bean,

  • 一种是  OpenTelemetry类型,
  • 另一种是  Resource类型。

您需要在 POM.xml 中添加以下依赖项

<dependencies>
<dependency>

    <groupId>io.opentelemetry.instrumentation</groupId>

    <artifactId>opentelemetry-spring-boot-starter</artifactId>

    <version>1.32.0-alpha</version>
</dependency>
</dependencies>

  • OpenTelemetry bean 将自动注册 SdkLoggerProvider或 SdkMeterProvider 类型的任何 bean(如果在上下文中找到)。
  • 资源的属性可以通过 management.opentelemetry.resource-attributes配置属性进行配置。

现在,一旦 Spring Boot 检测到  MeterProvider bean,它就会在  BatchSpanProcessor上注册它。

要跟踪 JDBC 连接,加入依赖:

<dependencies>
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-jdbc</artifactId>
    <version>1.32.0-alpha</version>
</dependency>
</dependencies>

您可以将 OpenTelemetry 驱动程序类和 JDBC URL(以 JDBC:otel 开头)添加到 application.properties。

spring.datasource.url=jdbc:otel:mysql:localhost:dbname
spring.datasource.driver-class-name=io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver

观察Java 21 的虚拟线程与操作系统线程
顾名思义,操作系统线程是操作系统生成的操作系统线程。将一部分可用线程提供给 JVM,然后 JVM 将它们分配给 Java 进程和多线程程序。传统上,Java 线程通常会被分配一个操作系统线程来完成其任务。由于操作系统线程是有限的,并且一些 Java 线程执行需要许多线程周期的长期计算,而其他一些 Java 线程只需要 CPU 周期(线程)的一小部分即可完成其任务,因此 Java 21 的 JVM 创建了轻量级线程称为虚拟线程来优化管理线程分配。

Java 21 运行时通过将大量虚拟线程映射到少量操作系统线程并对其进行有效管理,可以给人一种拥有大量线程的错觉。虚拟线程仅在在 CPU 上执行计算时消耗操作系统线程,并在完成其直接任务时释放它,而不是在它正在处理的请求的生命周期内保留它。虚拟线程现在始终支持线程局部变量,并且不需要(不应该)进行池化,因为它们的设计寿命很短并且具有浅调用堆栈。虚拟线程并不比操作系统线程快,而是能够实现更高吞吐量所需的更高并发性。

虚拟线程的上述属性需要密切观察它们,以获得更好的应用程序设计和后期制作性能。

Micrometer 的低开销分析和监控机制可以将应用程序代码中的事件(例如 Web 请求或 I/O 操作)与为该操作分配的正确虚拟线程相关联。它可以单步执行虚拟线程、显示调用堆栈并检查堆栈帧中的变量。

观察并发:采用 Java 21 的结构化并发及其如何改进并发编程
Java 21 引入了虚拟线程、作用域值和并发 API,以实现更好的并发编程。想象一下,从父线程创建子线程、共享变量而不存在并发更新或竞争条件的危险以及通过结构化并发 API 管理所有线程框架的能力如何为您的多线程代码创造奇迹。结构化并发将在不同线程中运行的相关任务组视为单个工作单元,从而简化错误处理和取消、提高可靠性并增强可观察性。现在,您可以轻松地以具有共享变量的分层树的形式以及线程转储来可视化您的线程。

因此,错误处理得到了改进,因为 API 使得做正确的事情变得非常容易,并且很难意外地做错误的事情:很难不注意到子线程的错误,无法停止或等待子线程,并且无法在错误情况下快速关闭。

由于父子线程关系被形式化并被工具识别,因此可观察性得到了提高。

Response handle() throws ExecutionException, InterruptedException {

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

Supplier  user  = scope.fork(() -> findUser());
Supplier order = scope.fork(() -> fetchOrder());

Map shipmentTracking = scope.fork(() -> trackShipment(order));
scope.join()            // Join the 3 subtasks
.throwIfFailed();  // ... and propagate errors
// 在这里,两个子任务都已成功,因此将它们的结果进行合成
return new Response(user.get(), order.get(), shipmentTracking.get());

}

}

上述代码:

  • 描述任务-子任务(或父-子任务)关系
  • 具有逻辑控制流,如果其中一个子任务失败,父任务也会随之失败
  • try-with-resources 语句的主体提供了一个词法范围,可限制所有线程的生命周期
  • 增强了可观察性,因为线程转储现在会清楚地显示任务层次结构,运行 findUser()、fetchOrder()、trackShipment() 的线程显示为作用域的子线程。
  • 通过定义明确的结构提供了清晰度,即设置子任务,等待它们完成或被取消,然后决定是 SUCCEED(处理成功完成的子任务的结果)还是 FAIL(如果抛出错误,子任务已经完成,因此无需再清理)。

Java 21 的这一新功能与Open Telemetry或Micrometer等强大工具相结合,有助于通过持续反馈改进并发编程。

Java 21 中的其他可观察性支持:-
除此之外,Java 21 还提供了高级日志记录功能,包括结构化日志记录和日志关联。这可以更好地跟踪和分析应用程序日志,从而更轻松地识别和解决问题。

另一个有用的工具是改进的Flight Recorder,它不断收集有关正在运行的 Java 应用程序的诊断和分析数据,而不会产生任何性能开销。Flight Recorder 中的分析信息包括线程执行详细信息、内存使用情况和垃圾收集器指标。
将这些工具与其他可观测性工具相结合可以简化性能评估、异常检测和故障纠正。