在 Spring Boot 应用中配置 OpenTelemetry

更新于 2025-12-30

Saikat Chakraborty 2023-02-08

1. 概述

在分布式系统中,服务请求过程中偶尔发生错误是不可避免的。一个集中式的可观测性平台可以通过捕获应用的追踪(traces)和日志(logs),并提供查询特定请求的界面,帮助我们快速定位问题。

OpenTelemetry 有助于标准化遥测数据(telemetry data)的采集与导出流程。

在本教程中,我们将学习如何通过 Micrometer 门面将 Spring Boot 应用与 OpenTelemetry 集成。同时,我们还将运行一个 OpenTelemetry 服务来捕获应用追踪数据,并将其发送到中央系统以监控请求。

首先,让我们了解一些基本概念。

2. OpenTelemetry 简介

OpenTelemetry(简称 Otel)是一套标准化、与厂商无关的工具、API 和 SDK 的集合。它是 CNCF(云原生计算基金会)的孵化项目,由 OpenTracing 和 OpenCensus 两个项目合并而成。

  • OpenTracing 提供了一套与厂商无关的 API,用于将遥测数据发送到可观测性后端。
  • OpenCensus 提供了一系列语言特定的库,开发者可以使用这些库对代码进行插桩(instrumentation),并将数据发送到任意支持的后端。

OpenTelemetry 延续了其前身项目中使用 trace(追踪)span(跨度) 来表示请求在微服务之间流转的概念。

OpenTelemetry 允许我们对应用进行插桩、生成并收集遥测数据,从而分析应用的行为或性能。遥测数据包括日志(logs)、指标(metrics)和追踪(traces)。我们可以对 HTTP 请求、数据库调用等操作进行自动或手动插桩。

Spring Boot 3 通过 Micrometer Tracing 支持 OpenTelemetry —— 这是一个一致的、可插拔的、与厂商无关的 API。

值得注意的是,早期的 Spring Cloud Sleuth 框架已在 Spring Boot 3 中被弃用,其追踪功能已迁移到 Micrometer Tracing。

接下来,我们通过一个示例深入探讨。

3. 示例应用

假设我们需要构建两个微服务,其中一个服务会调用另一个服务。

为了采集遥测数据,我们将把应用与 Micrometer Tracing 和 OpenTelemetry 导出器(exporter)库集成。

3.1. Maven 依赖

micrometer-tracingmicrometer-tracing-bridge-otelopentelemetry-exporter-otlp 依赖项可以自动捕获并导出追踪数据到任意支持的收集器(collector)。

首先,我们创建一个 Spring Boot 3 Web 项目,并在两个应用中添加以下 Spring Boot 3 Starter、Micrometer 和 OpenTelemetry 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.4.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>3.4.4</version>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing</artifactId>
    <version>1.4.4</version>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
    <version>1.4.4</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
    <version>1.39.0</version>
</dependency>

接下来,我们实现下游服务。

3.2. 实现下游应用(Price Service)

我们的下游应用将提供一个返回价格数据的端点。

首先,定义 Price 类:

public class Price {
    private long productId;
    private double priceAmount;
    private double discount;
}

然后,实现 PriceController,提供获取价格的接口:

@RestController(value = "/price")
public class PriceController {

    private static final Logger LOGGER = LoggerFactory.getLogger(PriceController.class);

    @Autowired
    private PriceRepository priceRepository;

    @GetMapping(path = "/{id}")
    public Price getPrice(@PathVariable("id") long productId) {
        LOGGER.info("Getting Price details for Product Id {}", productId);
        return priceRepository.getPrice(productId);
    }
}

接着,在 PriceRepository 中实现 getPrice() 方法:

public Price getPrice(Long productId){
    LOGGER.info("Getting Price from Price Repo With Product Id {}", productId);
    if (!priceMap.containsKey(productId)){
        LOGGER.error("Price Not Found for Product Id {}", productId);
        throw new PriceNotFoundException("Price Not Found");
    }
    return priceMap.get(productId);
}

上述代码中,如果找不到对应商品的价格,则抛出异常。

3.3. 实现上游应用(Product Service)

上游应用将提供一个获取商品详情的端点,并调用上述价格服务。

首先,定义 Product 类:

public class Product {
    private long id;
    private String name;
    private Price price;
}

然后,实现 ProductController

@RestController
public class ProductController {

    private static final Logger LOGGER = LoggerFactory.getLogger(ProductController.class);

    @Autowired
    private PriceClient priceClient;

    @Autowired
    private ProductRepository productRepository;

    @GetMapping(path = "/product/{id}")
    public Product getProductDetails(@PathVariable("id") long productId){
        LOGGER.info("Getting Product and Price Details with Product Id {}", productId);
        Product product = productRepository.getProduct(productId);
        product.setPrice(priceClient.getPrice(productId));
        return product;
    }
}

ProductRepository 中实现 getProduct() 方法:

public Product getProduct(Long productId){
    LOGGER.info("Getting Product from Product Repo With Product Id {}", productId);
    if (!productMap.containsKey(productId)){
        LOGGER.error("Product Not Found for Product Id {}", productId);
        throw new ProductNotFoundException("Product Not Found");
    }
    return productMap.get(productId);
}

此外,由于 Spring Boot 3 的要求,我们需要显式地通过 RestTemplateBuilder 定义 RestTemplate Bean:

@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
}

最后,在 PriceClient 中实现 getPrice() 方法:

public Price getPrice(@PathVariable("id") long productId){
    LOGGER.info("Fetching Price Details With Product Id {}", productId);
    String url = String.format("%s/price/%d", baseUrl, productId);
    ResponseEntity<Price> price = restTemplate.getForEntity(url, Price.class);
    return price.getBody();
}

上述代码中,我们调用了下游服务以获取价格信息。

4. 使用 OpenTelemetry 配置 Spring Boot

OpenTelemetry 提供了一个名为 Otel Collector(收集器) 的组件,用于处理遥测数据并将其导出到 Jaeger、Prometheus 等可观测性后端。

我们可以使用 Spring 的管理配置将追踪数据导出到任意 OpenTelemetry 收集器。

4.1. 配置 Spring

我们需要在 application.yml 中配置 management.tracing.sampling.probabilitymanagement.otlp.tracing.endpoint 属性,以导出追踪数据:

management:
  tracing:
    sampling:
      probability: '1.0'
  otlp:
    tracing:
      endpoint: http://collector:4318/v1/traces
  • sampling.probability: 1.0 表示 100% 的 span 都会被导出(适用于开发环境;生产环境通常使用更低的采样率)。
  • endpoint 指向 OpenTelemetry 收集器的 OTLP(OpenTelemetry Protocol)接收端点。

5. 运行应用

现在,我们将配置并运行整个环境:两个 Spring Boot 应用 + 一个 OpenTelemetry 收集器(例如 Jaeger)。

5.1. 为应用编写 Dockerfile

为 Product 服务创建 Dockerfile

FROM openjdk:17-alpine
COPY target/spring-cloud-open-telemetry1-1.0.0-SNAPSHOT.jar spring-cloud-open-telemetry.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/spring-cloud-open-telemetry.jar"]

Price 服务的 Dockerfile 基本相同。

5.2. 使用 Docker Compose 配置服务

创建 docker-compose.yml 文件,整合所有服务:

services:
  product-service:
    build: spring-boot-open-telemetry1/
    ports:
      - "8080:8080"

  price-service:
    build: spring-boot-open-telemetry2/
    ports:
      - "8081:8081"

  collector:
    image: jaegertracing/jaeger:2.5.0
    ports:
      - "4318:4318"
      - "16686:16686"

说明:

  • 我们使用了 jaegertracing/jaeger:2.5.0 的 all-in-one 镜像,它内置了 OpenTelemetry 支持,因此无需单独运行 OpenTelemetry Collector。
  • 端口 4318 用于接收 OTLP 格式的追踪数据。
  • 端口 16686 是 Jaeger UI 的访问端口。

架构图说明
上游(Product)和下游(Price)服务将 span 数据导出到 Jaeger v2 服务。Jaeger 内部包含三个核心组件:Collector(收集器)、Storage(存储)和 Query/UI(查询与界面)。

在生产环境中,建议将 Collector、Storage 和 UI 服务分离部署,以实现关注点分离。

启动服务:

$ docker-compose up

5.3. 验证运行中的 Docker 服务

使用以下命令查看容器状态:

$ docker container ls --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"

预期输出类似:

125c47300f69   spring-boot-open-telemetry-product-service-1   Up 19 seconds   0.0.0.0:8080->8080/tcp
5e8477630211   spring-boot-open-telemetry-price-service-1     Up 19 seconds   0.0.0.0:8081->8081/tcp
6ace8520779a   spring-boot-open-telemetry-collector-1         Up 19 seconds   0.0.0.0:4318->4318/tcp, 0.0.0.0:16686->16686/tcp

确认所有服务均已正常运行。

6. 在收集器中监控追踪数据

像 Jaeger 这样的 OpenTelemetry 收集器提供了前端界面,可用于实时或事后查看请求追踪。

我们将分别观察成功请求失败请求的追踪情况。

6.1. 监控成功请求的追踪

调用上游服务的端点:http://localhost:8080/product/100001

控制台日志示例:

product-service-1  | 2025-04-08T04:21:08.372Z  INFO ... [ca9845ffc9130c579d41f2f2ef61874a-ccb2d4cd80180fe9] ... Getting Product from Product Repo With Product Id 100001
product-service-1  | 2025-04-08T04:21:08.373Z  INFO ... [ca9845ffc9130c579d41f2f2ef61874a-ccb2d4cd80180fe9] ... Fetching Price Details With Product Id 100001
price-service-1    | 2025-04-08T04:21:08.731Z  INFO ... [ca9845ffc9130c579d41f2f2ef61874a-60bf6b4856b145f6] ... Getting Price details for Product Id 100001

关键点:

  • Micrometer 会自动将 trace IDspan ID 注入当前线程上下文,并作为 HTTP Header 传递给下游服务。
  • 下游服务(Price)也会自动将相同的 trace ID 关联到其日志和 span 中。
  • Jaeger 利用该 trace ID 将跨服务的请求串联起来。

打开 Jaeger UI(http://localhost:16686),可看到完整的请求链路。

图中展示了请求在各服务间的耗时、元数据及调用关系。

6.2. 监控失败请求的追踪

现在测试一个失败场景:调用 /product/100005,该商品在下游不存在。

Jaeger UI 中将显示带有错误标记的 span,并可追溯到抛出异常的具体位置。

通过该视图,我们可以快速定位故障根源(例如:PriceNotFoundException)。

7. 结论

本文介绍了 OpenTelemetry 如何帮助标准化微服务的可观测性模式。

我们演示了如何在 Spring Boot 3 应用中通过 Micrometer Tracing 门面集成 OpenTelemetry,并通过 Jaeger UI 可视化跨服务的请求追踪。

这种方案不仅提升了系统的可观测性,还能显著加快故障排查速度,是现代云原生应用不可或缺的组成部分。