1. 概述
在分佈式系統中,偶爾出現錯誤是不可避免的。一箇中心的可觀察性平台通過捕獲應用程序的跟蹤/日誌,並提供查詢特定請求的接口,從而發揮作用。 OpenTelemetry 幫助標準化捕獲和導出遙感數據的過程。
在本教程中,我們將學習如何使用 Micrometer facade 將 Spring Boot 應用程序與 OpenTelemetry 集成。 此外,我們還將運行一個 OpenTelemetry 服務,以捕獲應用程序跟蹤並將其發送到中心繫統以進行請求監控。
首先,讓我們瞭解一些基本概念。
2. OpenTelemetry 簡介
OpenTelemetry (簡稱 OTel) 是一套標準化的、與供應商無關的工具、API 和 SDK 集合。它是一個 CNCF 的孵化項目,是 OpenTracing 和 OpenCensus 項目的合併。
OpenTracing 是一個供應商中立的 API,用於將遙感數據發送到可觀測性後端。OpenCensus 項目提供了一組開發者可以用於對代碼進行工具化並將其發送到任何受支持後端的語言特定庫。 OTel 使用與其前身項目相同的 trace 和 span 概念,用於表示微服務之間請求流程。
OpenTelemetry 允許我們對應用程序進行工具化、生成和收集遙感數據,從而幫助分析應用程序的行為或性能。 遙感數據可以包括日誌、指標和跟蹤。 我們可以手動或自動地為 HTTP、數據庫調用等進行代碼工具化。
Spring Boot 3 通過 Micrometer Tracing 支持 OpenTelemetry – 一個一致的、可插拔的、供應商中立的 API。
值得注意的是,早期的 <strong title="Spring Cloud Sleuth 框架在 Spring Boot 3 中已棄用,其跟蹤功能已遷移到 Micrometer Tracing。”>Spring Cloud Sleuth 框架在 Spring Boot 3 中已棄用,其跟蹤功能已遷移到 Micrometer Tracing。
3. 示例應用程序
假設我們需要構建兩個微服務,其中一個服務與另一個服務交互。
為了為應用程序進行遙測數據採集,我們將應用程序與 Micrometer 追蹤和 OpenTelemetry 導出器庫集成。
3.1. Maven 依賴
`micrometer-tracing, micrometer-tracing-bridge-otel 和 opentelemetry-exporter-otlp 這些依賴項會自動捕獲和導出跟蹤信息到任何支持的收集器。
首先,我們將創建一個 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 數據。
首先,讓我們對 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);
}
}然後,我們將實現 getPrice() 方法,該方法位於 PriceRepository 中:
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. 實現上游應用
上游應用還將提供一個端點,用於獲取 產品 詳情並與上述獲取價格端點集成。
首先,讓我們實現 產品 類:
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;
}
}接下來,我們將實現 getProduct() 方法,該方法位於 ProductRepository 類中:
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);
}此外,我們需要明確定義 RestTemplate Bean,使用 RestTemplateBuilder,因為在 Spring Boot 版本 3 中是必需的:
@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 收集器(Otel collector)的組件,用於處理和導出遙測數據到各種可觀測性後端,例如 Jaeger, Prometheus 等。
使用 Spring 管理配置,可以將追蹤數據導出到任何 OpenTelemetry 收集器。
4.1. 配置 Spring
我們需要配置應用程序,使用 <em >management</em>、<em >management.tracing.sampling.probability</em> 和 <em >management.otlp.tracing.endpoint</em> 屬性來導出跟蹤數據。
讓我們在 <em >application.yml</em> 中包含管理配置:
management:
tracing:
sampling:
probability: '1.0'
otlp:
tracing:
endpoint: http://collector:4318/v1/traces該 基於追蹤採樣概率的特性 定義了收集到的 span 的採樣比例。 值 1.0 表示所有 span 都將被導出。
5. 運行應用程序
現在我們將配置並運行整個設置、應用程序以及像 Jaeger 這樣的 Otel 收集器服務,以便快速啓動。
5.1. 配置 Dockerfile 在應用程序中
讓我們為我們的產品服務實現 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"]我們應該注意的是,價格服務的 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"
collector:
image: jaegertracing/jaeger:2.5.0
ports:
- "4318:4318"
- "16686:16686"在上述 collector 服務中,我們使用了 jaegertracing/jaeger v2 的一體化鏡像。它現在內置了對 OpenTelemetry 的支持,因此我們不需要單獨運行 OpenTelemetry 服務。
以下是參與服務的總體架構:
在上述架構圖中,我們實現了向 Jaeger v2 服務導出 span 的 API 服務。 Jaeger v2 服務內部包含三個主要組件,即 collector/receiver、存儲和 UI 服務。
請注意,對於任何生產級別的可觀測性設置,我們理想情況下應該分別運行 OpenTelemetry collector、存儲和 Query/UI 服務,以確保分層架構。
現在,讓我們通過 docker-compose 運行服務。
$ docker-compose up我們現在將驗證正在運行的 Docker 服務。
5.3. 驗證運行中的 Docker 服務
除了 product-service 和 price-service,我們還將 Jaeger 服務集成到整個配置中。
上述 product-service 和 price-service 使用 HTTP 端口 4318 將跟蹤數據發送到 Jaeger 收集器。
讓我們使用 docker container 命令來驗證服務的狀態:
$ 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:49775->8081/tcp
6ace8520779a spring-boot-open-telemetry-collector-1 Up 19 seconds 4317-4318/tcp, 5778-5779/tcp, 9411/tcp, 13132-13133/tcp, 14250/tcp, 14268/tcp, 0.0.0.0:16686->16686/tcp
從以上命令輸出結果來看,我們確認所有服務正在運行。
6. 監控收集器中的跟蹤
OpenTelemetry 收集器工具,如 Jaeger,也提供前端應用程序,用於監控請求。我們可以實時或稍後查看請求跟蹤。
讓我們監控請求成功以及失敗時的跟蹤。
6.1. 成功請求時監控跟蹤
首先,調用產品端點:http://localhost:8080/product/100001。
請求會產生以下日誌:
product-service-1 | 2025-04-08T04:21:08.372Z INFO 1 --- [product-service] [nio-8080-exec-1] [ca9845ffc9130c579d41f2f2ef61874a-ccb2d4cd80180fe9] c.b.o.repository.ProductRepository : Getting Product from Product Repo With Product Id 100001
product-service-1 | 2025-04-08T04:21:08.373Z INFO 1 --- [product-service] [nio-8080-exec-1] [ca9845ffc9130c579d41f2f2ef61874a-ccb2d4cd80180fe9] c.b.o.api.client.PriceClient : Fetching Price Details With Product Id 100001
price-service-1 | 2025-04-08T04:21:08.731Z INFO 1 --- [price-service] [nio-8081-exec-1] [ca9845ffc9130c579d41f2f2ef61874a-60bf6b4856b145f6] c.b.o.controller.PriceController : Getting Price details for Product Id 100001
Micrometer 將會自動配置 <em ProductService 以將 <em trace 和 span id 附加到當前線程上下文中,並作為下行 API 調用中的 HTTP Header。 <em PriceService 也會自動將相同的 <em trace id 包含在線程上下文中和日誌中。 Jaeger 收集器服務將使用此 trace id 來確定跨服務的請求流。
正如預期的那樣,上述 <em trace id ….f61874a 在 <em PriceService 和 <em ProductService 的日誌中都是相同的。
讓我們在 Jaeger UI 中可視化整個請求 span 時間線,該 UI 託管在端口 <em>16686:
上述內容顯示了請求流的時間線,幷包含用於表示請求的元數據。
6.2. 監控請求失敗時的跟蹤
設想一個場景,其中下游服務拋出異常,導致請求失敗。
再次,我們將使用相同的 UI 來分析根本原因。
讓我們使用 Product 端點 /product/100005 進行測試,該端點調用失敗,因為 Product 在下游應用程序中不存在。
現在,讓我們可視化失敗的請求跨度:
如上所示,我們可以追溯請求到最終的 API 調用,該調用是錯誤的來源。
7. 結論
在本文中,我們學習了OpenTelemetry如何幫助微服務標準化可觀測性模式。
我們還看到了如何使用Micrometer追蹤面料與示例配置Spring Boot 3應用程序,以及在Jaeger UI服務中跟蹤API請求流程。