Spring Boot 3 和 Observability(可观察性)
1、概览
在本文中,我们将学习如何在 Spring Boot 3 配置可观察性(observability)。可观察性是一种仅通过外部输出来衡量系统内部状态的能力。
此外,我们必须注意 Spring Boot 2(Spring 5)和 Spring Boot 3(Spring 6)之间的重大变化。Spring 6 引入了 Spring Observability,这是一项建立在 Micrometer 和 Micrometer Tracing(原 Spring Cloud Sleuth)基础上的新举措。它更适合使用 Micrometer 高效记录应用指标,并通过 OpenZipkin 的 Brave 或 OpenTelemetry 等 provider 实现追踪。Spring Observability 优于“基于代理的可观察性解决方案”,因为它能在原生编译的 Spring 应用程序中无缝运行,并能更有效地提供更好的信息。
我们只介绍有关 Spring Boot 3 的详细信息。如果要从 Spring Boot 2 迁移,可以在这里找到详细说明。
2、Micrometer Observation API
Micrometer 是一个独立于供应商中立的“应用度量 facade”的项目。它定义了 meter、rate aggregation、counter、gauge 和 timer 等概念,每个供应商都可以根据自己的概念和工具进行调整。其中一个核心部分是 Observation API,它允许对代码进行一次性的仪表化,并具有多种优势。
仪表化是指在代码中添加监测点或仪表,以便收集关于代码执行过程的信息。这些监测点可以是特定的观测点或代码片段,用于捕获代码的执行路径、变量状态、函数调用和其他相关信息。这些信息对于代码性能分析、调试和优化非常有用。
它是 Spring Framework 多个部分的依赖项之一,因此我们需要了解这个 API,才能理解 Spring Boot 中的观察功能是如何工作的。我们可以通过一个简单的示例来实现这一点。
2.1、Observation 和 ObservationRegistry
根据 dictionary.com 的解释,“观察(observation)” 是指 “出于某种科学或其他特殊目的,查看或记录某一事实或事件的行为或实例”。在我们的代码中,我们可以观察单个操作或完整的 HTTP 请求处理。在这些观察中,我们可以进行测量、创建用于分布式追踪的 span 或仅记录额外信息。
要创建观察,我们需要一个 ObservationRegistry
。
ObservationRegistry observationRegistry = ObservationRegistry.create();
Observation observation = Observation.createNotStarted("sample", observationRegistry);
观察的生命周期如下图所示,非常简单:
我们可以这样使用 Observation
:
observation.start();
try (Observation.Scope scope = observation.openScope()) {
// ... 被观察的操作
} catch (Exception e) {
observation.error(e);
// 其他异常处理
} finally {
observation.stop();
}
或者干脆:
observation.observe(() -> {
// ... 被观察的操作
});
2.2、ObservationHandler
数据收集通过 ObservationHandler
的实现完成。该 handler 会通过其回调方法收到有关观察生命周期事件的通知。
一个只是打印出事件的简单 handler 实现如下:
public class SimpleLoggingHandler implements ObservationHandler<Observation.Context> {
private static final Logger log = LoggerFactory.getLogger(SimpleLoggingHandler.class);
@Override
public boolean supportsContext(Observation.Context context) {
return true;
}
@Override
public void onStart(Observation.Context context) {
log.info("Starting");
}
@Override
public void onScopeOpened(Observation.Context context) {
log.info("Scope opened");
}
@Override
public void onScopeClosed(Observation.Context context) {
log.info("Scope closed");
}
@Override
public void onStop(Observation.Context context) {
log.info("Stopping");
}
@Override
public void onError(Observation.Context context) {
log.info("Error");
}
}
然后,我们在创建观察之前,在 ObservationRegistry
注册 ObservationHandler
:
observationRegistry
.observationConfig()
.observationHandler(new SimpleLoggingHandler());
对于简单的日志记录,已经有了预定义的实现。例如,要简单地将事件记录到控制台,我们可以使用:
observationRegistry
.observationConfig()
.observationHandler(new ObservationTextPublisher(System.out::println));
要使用 timer sample (定时器采样)和 counter (计数器),我们可以这样配置:
MeterRegistry meterRegistry = new SimpleMeterRegistry();
observationRegistry
.observationConfig()
.observationHandler(new DefaultMeterObservationHandler(meterRegistry));
// ... 使用名称为 "sample" 的 Observation 进行观察
// 获取指定观测的最长持续时间
Optional<Double> maximumDuration = meterRegistry.getMeters().stream()
.filter(m -> "sample".equals(m.getId().getName()))
.flatMap(m -> StreamSupport.stream(m.measure().spliterator(), false))
.filter(ms -> ms.getStatistic() == Statistic.MAX)
.findFirst()
.map(Measurement::getValue);
3、 Spring 整合
3.1、 Actuator
通过 Actuator 依赖,我们在 Spring Boot 应用中实现了最佳整合:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
它包含一个 ObservationAutoConfiguration
,可提供 ObservationRegistry
的可注入实例(如果还不存在),并配置 ObservationHandlers
用于收集 metric(度量)和 trace(追踪)。
例如,我们可以使用 registry 在服务中创建自定义 observation:
@Service
public class GreetingService {
private ObservationRegistry observationRegistry;
// constructor
public String sayHello() {
return Observation
.createNotStarted("greetingService", observationRegistry)
.observe(this::sayHelloNoObserver);
}
private String sayHelloNoObserver() {
return "Hello World!";
}
}
此外,它还会在 ObservationRegistry
注册 ObservationHandler
Bean。我们只需提供 Bean:
@Configuration
public class ObservationTextPublisherConfiguration {
private static final Logger log = LoggerFactory.getLogger(ObservationTextPublisherConfiguration.class);
@Bean
public ObservationHandler<Observation.Context> observationTextPublisher() {
return new ObservationTextPublisher(log::info);
}
}
3.2、Web
对于 MVC 和 WebFlux,有一些 Filter 可用于 HTTP 服务器观察:
org.springframework.web.filter.ServerHttpObservationFilter
用于 Spring MVCorg.springframework.web.filter.reactive.ServerHttpObservationFilter
用于 WebFlux
当 Actuator
已经整合进应用中时,这些 filter 已经注册并激活。如果没有,我们需要对它们进行配置:
@Configuration
public class ObservationFilterConfiguration {
// 配置了 ObservationRegistry
@ConditionalOnBean(ObservationRegistry.class)
// 未使用 Actuator
@ConditionalOnMissingBean(ServerHttpObservationFilter.class)
@Bean
public ServerHttpObservationFilter observationFilter(ObservationRegistry registry) {
return new ServerHttpObservationFilter(registry);
}
}
有关 Spring Web 中可观察性整合的更多详情,请 参阅文档。
3.3、AOP
Micrometer Observation API 还声明了一个 @Observed
注解,其中包含一个基于 AspectJ 的切面实现。要实现这一点,我们需要在项目中添加 AOP 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后,我们将 aspect 实现注册为 Spring 管理的 Bean:
@Configuration
public class ObservedAspectConfiguration {
@Bean
public ObservedAspect observedAspect(ObservationRegistry observationRegistry) {
return new ObservedAspect(observationRegistry);
}
}
现在,我们不用在代码中创建一个 Observation
,可以快速编写 GreetingService
:
@Observed(name = "greetingService")
@Service
public class GreetingService {
public String sayHello() {
return "Hello World!";
}
}
结合 Actuator,我们可以使用 http://localhost:8080/actuator/metrics/greetingService
读出预先配置的指标(至少调用一次服务后),并得到如下结果:
{
"name": "greetingService",
"baseUnit": "seconds",
"measurements": [
{
"statistic": "COUNT",
"value": 15
},
{
"statistic": "TOTAL_TIME",
"value": 0.0237577
},
{
"statistic": "MAX",
"value": 0.0035475
}
],
...
}
4、测试观察
Micrometer Observability API 提供了一个允许编写测试的模块。为此,我们需要添加此依赖:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation-test</artifactId>
<scope>test</scope>
</dependency>
micrometer-bom
是 Spring Boot 受管依赖的一部分,因此我们无需指定任何版本。
由于整个可观察性的自动配置在默认情况下对测试是禁用的,因此只要我们想测试默认的观察,就需要使用 @AutoConfigureObservability
重新启用它。
4.1、TestObservationRegistry
我们可以使用允许基于 AssertJ 断言的 TestObservationRegistry
。因此,我们必须用 TestObservationRegistry
实例替换上下文中已有的 ObservationRegistry
。
因此,举例来说,如果我们要测试对 GreetingService
的观察,可以使用这种测试设置:
@ExtendWith(SpringExtension.class)
@ComponentScan(basePackageClasses = GreetingService.class)
@EnableAutoConfiguration
@Import(ObservedAspectConfiguration.class)
@AutoConfigureObservability
class GreetingServiceObservationTest {
@Autowired
GreetingService service;
@Autowired
TestObservationRegistry registry;
@TestConfiguration
static class ObservationTestConfiguration {
@Bean
TestObservationRegistry observationRegistry() {
return TestObservationRegistry.create();
}
}
// ...
}
我们也可以使用 JUnit 元注解进行配置:
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
ObservedAspectConfiguration.class,
EnableTestObservation.ObservationTestConfiguration.class
})
@AutoConfigureObservability
public @interface EnableTestObservation {
@TestConfiguration
class ObservationTestConfiguration {
@Bean
TestObservationRegistry observationRegistry() {
return TestObservationRegistry.create();
}
}
}
然后,我们只需在测试类中添加注解:
@ExtendWith(SpringExtension.class)
@ComponentScan(basePackageClasses = GreetingService.class)
@EnableAutoConfiguration
@EnableTestObservation
class GreetingServiceObservationTest {
@Autowired
GreetingService service;
@Autowired
TestObservationRegistry registry;
// ...
}
然后,我们就可以调用服务,检查观察是否完成:
import static io.micrometer.observation.tck.TestObservationRegistryAssert.assertThat;
// ...
@Test
void testObservation() {
// invoke service
service.sayHello();
assertThat(registry)
.hasObservationWithNameEqualTo("greetingService")
.that()
.hasBeenStarted()
.hasBeenStopped();
}
4.2、Observation Handler 兼容性套件
为了测试我们的 ObservationHandler
实现,我们可以在测试中继承一些基类(即所谓的兼容性工具包):
NullContextObservationHandlerCompatibilityKit
测试 observation handler 是否能在参数为 null 值的情况下正常工作。AnyContextObservationHandlerCompatibilityKit
测试 observation handler 在未指定测试上下文参数的情况下是否正常工作。这也包括NullContextObservationHandlerCompatibilityKit
。ConcreteContextObservationHandlerCompatibilityKit
测试 observation handler 在 test context 类型的情况下是否能正常工作。
实现很简单:
public class SimpleLoggingHandlerTest
extends AnyContextObservationHandlerCompatibilityKit {
SimpleLoggingHandler handler = new SimpleLoggingHandler();
@Override
public ObservationHandler<Observation.Context> handler() {
return handler;
}
}
输出结果如下:
5、Micrometer 追踪(Tracing)
自 Spring Boot 3 起,原 Spring Cloud Sleuth 项目已转移到 Micrometer,这是 Micrometer 追踪的核心。我们可以在 文档 中找到 Micrometer 追踪的定义:
Micrometer Tracing 为最流行的追踪库提供了一个简单的 facade(门面),让你可以对基于 JVM 的应用程序代码进行检测,而无需固定供应商(实现)。它的设计目的是在最大限度提高追踪工作可移植性的同时,几乎不增加追踪收集活动的开销。
我们可以单独使用它,但它也可以通过提供 ObservationHandler
的继承与 Observation API 整合。
5.1、整合 Observation API
要使用 Micrometer Tracing,我们需要在项目中添加以下依赖,该版本由 Spring Boot 管理:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
然后,我们需要一个 支持的 tracer(目前是 OpenZipkin Brave 或 OpenTelemetry)。然后,我们必须为特定供应商整合到 Micrometer Tracing 添加一个依赖:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
或:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
Spring Actuator 可自动配置两种 tracer,即注册特定于供应商的对象和 Micrometer Tracing ObservationHandler
实现,并将这些对象委托给 application context。因此,无需进一步的配置步骤。
5.2、测试的支持
出于测试目的,我们需要在项目中添加以下依赖 - 该版本由 Spring Boot 管理:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId>
<scope>test</scope>
</dependency>
然后,我们就可以在测试过程中使用 SimpleTracer
类来收集和验证追踪数据。要使其正常工作,我们需要在 application context 中用 SimpleTracer
类替换原有的、特定于供应商的 Tracer
类。此外,我们还必须记住使用 @AutoConfigureObservability
(自动配置可观察性)启用自动配置追踪功能。
因此,用于追踪的最低测试配置可以是这样的:
@ExtendWith(SpringExtension.class)
@EnableAutoConfiguration
@AutoConfigureObservability
public class GreetingServiceTracingTest {
@TestConfiguration
static class ObservationTestConfiguration {
@Bean
TestObservationRegistry observationRegistry() {
return TestObservationRegistry.create();
}
@Bean
SimpleTracer simpleTracer() {
return new SimpleTracer();
}
}
@Test
void shouldTrace() {
// test
}
}
或者使用 JUnit 元注解:
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@AutoConfigureObservability
@Import({
ObservedAspectConfiguration.class,
EnableTestObservation.ObservationTestConfiguration.class
})
public @interface EnableTestObservation {
@TestConfiguration
class ObservationTestConfiguration {
@Bean
TestObservationRegistry observationRegistry() {
return TestObservationRegistry.create();
}
@Bean
SimpleTracer simpleTracer() {
return new SimpleTracer();
}
}
}
然后,我们可以通过以下示例测试来测试我们的 GreetingService
:
import static io.micrometer.tracing.test.simple.TracerAssert.assertThat;
// ...
@Autowired
GreetingService service;
@Autowired
SimpleTracer tracer;
// ...
@Test
void testTracingForGreeting() {
service.sayHello();
assertThat(tracer)
.onlySpan()
.hasNameEqualTo("greeting-service#say-hello")
.isEnded();
}
6、总结
在本教程中,我们探索了 Micrometer Observation API 以及与 Spring Boot 3 的整合。我们了解到 Micrometer 是独立于供应商的 API,而 Micrometer Tracing 是一种扩展。我们了解到 Actuator 有一套预配置的 observation 和 tracing,而且默认情况下测试的可观察性自动配置是禁用的。
参考:https://www.baeldung.com/spring-boot-3-observability