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

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 MVC
  • org.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 BraveOpenTelemetry)。然后,我们必须为特定供应商整合到 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