Ralf Ueberfuhr 2022-12-31
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 等提供者实现追踪功能。相比基于代理(agent-based)的可观测性解决方案,Spring Observability 在原生编译的 Spring 应用中无缝运行,并能更有效地提供更丰富的信息。
本文将仅聚焦于 Spring Boot 3 的相关内容。如果你希望从 Spring Boot 2 迁移,可参考官方提供的详细迁移指南。
2. Micrometer Observation API
Micrometer 是一个提供厂商中立的应用指标门面(facade)的项目。它定义了诸如计量器(meters)、速率聚合(rate aggregation)、计数器(counters)、仪表(gauges)和计时器(timers)等概念,各监控后端厂商可根据自身工具适配这些概念。
其中核心部分是 Observation API,它允许我们只需对代码进行一次插桩(instrumentation),即可获得多种好处。
由于该 API 已被集成到 Spring Framework 的多个模块中,因此要理解 Spring Boot 中的可观测性机制,我们需要掌握此 API。下面通过一个简单示例说明。
2.1. Observation 与 ObservationRegistry
根据 dictionary.com 的定义,“observation” 是“为某种科学或其他特定目的而观察或记录某一事实或事件的行为”。在我们的代码中,我们可以对单个操作或完整的 HTTP 请求处理过程进行观测。在这些观测过程中,我们可以采集指标、创建用于分布式追踪的 span,或者仅记录额外信息。
要创建一个观测(Observation),我们需要一个 ObservationRegistry:
ObservationRegistry observationRegistry = ObservationRegistry.create();
Observation observation = Observation.createNotStarted("sample", observationRegistry);
观测具有如下所示的生命周期:
Observation StateChart 图略
我们可以这样使用 Observation:
observation.start();
try (Observation.Scope scope = observation.openScope()) {
// ... 被观测的操作
} catch (Exception e) {
observation.error(e);
// 其他异常处理
} finally {
observation.stop();
}
或者更简洁地:
observation.observe(() -> {
// ... 被观测的操作
});
2.2. ObservationHandler
数据收集逻辑由 ObservationHandler 实现。该处理器会监听 Observation 的生命周期事件,并提供相应的回调方法。以下是一个仅打印事件日志的简单处理器实现:
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");
}
}
然后在创建 Observation 前,将该处理器注册到 ObservationRegistry:
observationRegistry
.observationConfig()
.observationHandler(new SimpleLoggingHandler());
对于简单的日志输出,Micrometer 已提供现成实现。例如,若想将事件打印到控制台,可使用:
observationRegistry
.observationConfig()
.observationHandler(new ObservationTextPublisher(System.out::println));
若要使用计时器采样和计数器,可配置如下:
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
在 Spring Boot 应用中,最佳集成方式是引入 Actuator 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Actuator 包含 ObservationAutoConfiguration,它会自动提供一个可注入的 ObservationRegistry 实例(如果上下文中尚不存在),并配置用于收集指标和追踪的 ObservationHandler。
例如,我们可以在服务中使用该注册表创建自定义观测:
@Service
public class GreetingService {
private ObservationRegistry observationRegistry;
// 构造函数注入
public String sayHello() {
return Observation
.createNotStarted("greetingService", observationRegistry)
.observe(this::sayHelloNoObserver);
}
private String sayHelloNoObserver() {
return "Hello World!";
}
}
此外,只要我们将 ObservationHandler 定义为 Spring Bean,Actuator 就会自动将其注册到 ObservationRegistry:
@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,Spring 提供了一个过滤器 ServerHttpObservationFilter 用于 HTTP 服务器端的观测。当 Actuator 存在于应用中时,该过滤器会自动注册并启用。否则,需手动配置:
@Configuration
public class ObservationFilterConfiguration {
@ConditionalOnBean(ObservationRegistry.class)
@ConditionalOnMissingBean(ServerHttpObservationFilter.class)
@Bean
public ServerHttpObservationFilter observationFilter(ObservationRegistry registry) {
return new ServerHttpObservationFilter(registry);
}
}
对于 WebFlux,也存在类似的过滤器(org.springframework.web.filter.reactive.ServerHttpObservationFilter),但自 Spring Framework 6.1(即 Spring Boot 3.2)起已被弃用。取而代之的是通过 WebHttpHandlerBuilder 实现。若使用 Actuator,则会自动完成配置。
3.3. AOP 支持
Micrometer Observation API 还提供了一个 @Observed 注解,其切面(Aspect)实现基于 AspectJ。要启用此功能,需添加 AOP 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后将切面实现注册为 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>
由于 Spring Boot 默认在测试中禁用可观测性自动配置,因此需使用 @AutoConfigureObservability 注解重新启用。
4.1. TestObservationRegistry
我们可以使用 TestObservationRegistry 进行基于 AssertJ 的断言。为此,需用 TestObservationRegistry 实例替换上下文中原有的 ObservationRegistry。
例如,测试 GreetingService 的观测行为:
@ExtendWith(SpringExtension.class)
@ComponentScan(basePackageClasses = GreetingService.class)
@EnableAutoConfiguration
@Import(ObservedAspectConfiguration.class)
@AutoConfigureObservability
class GreetingServiceObservationIntegrationTest {
@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 GreetingServiceObservationIntegrationTest {
@Autowired
GreetingService service;
@Autowired
TestObservationRegistry registry;
// ...
}
接着即可调用服务并验证观测是否发生:
import static io.micrometer.observation.tck.TestObservationRegistryAssert.assertThat;
@Test
void testObservation() {
service.sayHello();
assertThat(registry)
.hasObservationWithNameEqualTo("greetingService")
.that()
.hasBeenStarted()
.hasBeenStopped();
}
4.2. Observation Handler 兼容性套件(Compatibility Kits)
为测试自定义的 ObservationHandler 实现,Micrometer 提供了几种基类(称为 Compatibility Kits):
NullContextObservationHandlerCompatibilityKit:测试处理器在参数为 null 时的行为。AnyContextObservationHandlerCompatibilityKit:测试处理器在任意上下文(包括 null)下的行为(包含上述 kit)。ConcreteContextObservationHandlerCompatibilityKit:测试处理器在具体上下文类型下的行为。
使用示例:
public class SimpleLoggingHandlerUnitTest
extends AnyContextObservationHandlerCompatibilityKit {
SimpleLoggingHandler handler = new SimpleLoggingHandler();
@Override
public ObservationHandler<Observation.Context> handler() {
return handler;
}
}
这将生成兼容性测试结果。
5. Micrometer Tracing
原 Spring Cloud Sleuth 项目已迁移至 Micrometer,其核心部分自 Spring Boot 3 起成为 Micrometer Tracing。官方文档对其定义如下:
Micrometer Tracing 为最流行的追踪库提供了简单的门面,使你能在不被厂商锁定的前提下对基于 JVM 的应用进行插桩。它旨在以极低甚至零开销收集追踪数据,同时最大化追踪工作的可移植性。
它可独立使用,也通过 ObservationHandler 扩展与 Observation API 集成。
5.1. 与 Observation API 集成
要使用 Micrometer Tracing,需添加以下依赖(版本由 Spring Boot 管理):
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
接着选择一个支持的追踪器(目前支持 OpenZipkin Brave 或 OpenTelemetry),并添加对应的桥接依赖:
<!-- 使用 Brave -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
或
<!-- 使用 OpenTelemetry -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
Spring Actuator 为这两种追踪器都提供了自动配置:它会注册厂商特定的对象以及 Micrometer Tracing 的 ObservationHandler 实现,并将其注入应用上下文。因此无需额外配置。
5.2. 测试支持
测试时需添加:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId>
<scope>test</scope>
</dependency>
然后可使用 SimpleTracer 在测试中收集和验证追踪数据。需在上下文中用 SimpleTracer 替换原始的厂商特定 Tracer,并记得使用 @AutoConfigureObservability 启用追踪自动配置。
最小测试配置如下:
@ExtendWith(SpringExtension.class)
@EnableAutoConfiguration
@AutoConfigureObservability
public class GreetingServiceTracingIntegrationTest {
@TestConfiguration
static class ObservationTestConfiguration {
@Bean
TestObservationRegistry observationRegistry() {
return TestObservationRegistry.create();
}
@Bean
SimpleTracer simpleTracer() {
return new SimpleTracer();
}
}
@Test
void shouldTrace() {
// 测试逻辑
}
}
或使用元注解:
@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();
}
}
}
测试示例:
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 是其扩展,用于分布式追踪;
- Spring Boot Actuator 提供了预配置的观测与追踪能力;
- 默认情况下,测试中会禁用可观测性自动配置,需显式启用。
通过合理使用这些工具,我们可以构建出高度可观测、易于调试和监控的现代 Spring Boot 应用。