Spring Boot + OpenTelemetry 实现分布式跟踪-Spring专区论坛-技术-SpringForAll社区

Spring Boot + OpenTelemetry 实现分布式跟踪

d2b5ca33bd20250103112441

理解分布式追踪

在微服务架构中,追踪是最具挑战性的部分。分布式追踪是解决这一问题的方案,它允许开发者监控和可视化请求在复杂的分布式系统中的流动。

什么是 OpenTelemetry?

OpenTelemetry 是一个与供应商无关的标准,提供了跨分布式应用程序的指标、日志和追踪功能。这有助于实现全面的可观测性,而不会被锁定在特定的监控平台中。OpenTelemetry 还提供了:

  1. 自动和手动的代码插桩
  2. 与多种后端系统的兼容性

使用 OpenTelemetry 配置 Spring Boot 3 应用程序

我们将为 Gradle 应用程序配置 OpenTelemetry。以下是我所做的更改。

项目结构:

我们将创建两个微服务:

  • 订单服务(端口 8084):处理订单操作并调用价格服务
  • 价格服务(端口 8083):提供价格信息
opentelemetry-spring-boot/
├── order-service/
│   ├── src/
│   └── build.gradle
├── price-service/
│   ├── src/
│   └── build.gradle
├── build.gradle
├── settings.gradle
├── docker-compose.yml
└── otel-config.yml

核心依赖:

首先,让我们了解我们使用的关键依赖项:

dependencies {
    // Spring Boot core dependencies
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'

    // OpenTelemetry core dependencies
    implementation 'io.micrometer:micrometer-registry-prometheus'
    implementation 'io.micrometer:micrometer-tracing-bridge-otel'
    implementation 'io.opentelemetry:opentelemetry-api'
    implementation 'io.opentelemetry:opentelemetry-sdk'
    implementation 'io.opentelemetry:opentelemetry-exporter-otlp'
    implementation 'io.opentelemetry.instrumentation:opentelemetry-instrumentation-api'
    implementation 'io.opentelemetry:opentelemetry-exporter-logging'
}

关键依赖解释:

  • micrometer-tracing-bridge-otel:将 Micrometer 追踪与 OpenTelemetry 桥接
  • opentelemetry-api:OpenTelemetry 核心 API
  • opentelemetry-sdk:OpenTelemetry API 的实现
  • opentelemetry-exporter-otlp:使用 OTLP 协议导出遥测数据

服务实现:

订单服务:

@RestController
@RequestMapping("/orders")
public class OrderController {
    private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);
    private final PriceGateway priceGateway;

    @Autowired
    public OrderController(PriceGateway priceGateway) {
        this.priceGateway = priceGateway;
    }

    @GetMapping("/{id}")
    public Order findById(@PathVariable Long id) {
        // Log with trace context
        LOGGER.info("Processing order request for id: {}", id);
        
        // Call price service through gateway
        Price price = priceGateway.getPrice(id);
        
        // Create and return order
        return new Order(id, 1L, ZonedDateTime.now(), price.getAmount());
    }
}

价格网关(对分布式追踪至关重要):

@Component
public class PriceGateway {
    private final RestTemplate restTemplate;
    private static final Logger LOGGER = LoggerFactory.getLogger(PriceGateway.class);
    private static final String BASE_URL = "http://localhost:8083";

    @Autowired
    public PriceGateway(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public Price getPrice(long productId) {
        LOGGER.info("Fetching price details for product: {}", productId);
        String url = String.format("%s/prices/%d", BASE_URL, productId);
        
        try {
            ResponseEntity<Price> response = restTemplate.getForEntity(url, Price.class);
            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                return response.getBody();
            }
            throw new RuntimeException("Failed to fetch price");
        } catch (Exception e) {
            LOGGER.error("Error fetching price: {}", e.getMessage());
            throw new RuntimeException("Price service communication failed", e);
        }
    }
}

RestTemplate 配置(对追踪传播至关重要):

@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

价格服务:

@RestController
@RequestMapping("/prices")
public class PriceController {
    private final static Logger LOGGER = LoggerFactory.getLogger(PriceController.class);

    @GetMapping("/{id}")
    public Price findById(@PathVariable Long id) {
        LOGGER.info("Retrieving price for product: {}", id);
        
        // Simulate some processing time
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        return new Price(id, BigDecimal.valueOf(Math.random() * 100));
    }
}

OpenTelemetry 配置

应用程序属性:

属性文件对于正确设置 OpenTelemetry 至关重要:

# Service Configuration
spring.application.name=order-service
server.port=8084

# OpenTelemetry Core Configuration
otel.service.name=${spring.application.name}
otel.exporter.otlp.endpoint=http://localhost:4317
otel.traces.exporter=otlp
otel.metrics.exporter=otlp
otel.logs.exporter=otlp

# Sampling Configuration
management.tracing.sampling.probability=1.0

# Logging Pattern (includes trace and span IDs)
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]

# Instrumentation Settings
otel.instrumentation.spring-webmvc.enabled=true
otel.instrumentation.spring-webflux.enabled=true
otel.resource.attributes=deployment.environment=development

OpenTelemetry 收集器配置:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  attributes:
    actions:
      - key: service.name
        action: upsert
        from_attribute: service.name

exporters:
  logging:
    loglevel: debug
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [logging, jaeger]

Docker 设置

服务 Dockerfile:

FROM eclipse-temurin:17-jdk
WORKDIR /app
COPY build/libs/*.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","app.jar"]

Docker Compose 配置:

version: '3.8'
services:
  order-service:
    build:
      context: ./order-service
      dockerfile: Dockerfile
    ports:
      - "8084:8084"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
    networks:
      - otel-network

  price-service:
    build:
      context: ./price-service
      dockerfile: Dockerfile
    ports:
      - "8083:8083"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
    networks:
      - otel-network

  otel-collector:
    image: otel/opentelemetry-collector:0.88.0
    volumes:
      - ./otel-config.yml:/etc/otel-collector-config.yml
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
      - "8888:8888"   # Prometheus metrics
    networks:
      - otel-network

  jaeger:
    image: jaegertracing/all-in-one:1.47
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - "16686:16686" # UI
      - "14250:14250" # Collector
    networks:
      - otel-network

networks:
  otel-network:
    driver: bridge

现在代码更改已完成。让我们构建应用程序。

运行和测试

构建项目:./gradlew clean build

启动 Docker 容器:docker-compose up --build

验证服务是否正在运行:docker ps

运行此命令后,您可以看到类似以下的结果:

d2b5ca33bd20250103113857

然后您需要测试端点。为此,您可以使用 curl 命令或 Postman:

curl http://localhost:8080/product/1
curl http://localhost:8081/price/1

如果 API 调用失败,您可以使用以下命令进行验证:

# View Product Service logs
docker-compose logs -f product-service

# View Price Service logs
docker-compose logs -f price-service

# View OpenTelemetry Collector logs
docker-compose logs -f otel-collector

# View Jaeger logs
docker-compose logs -f jaeger

访问 Jaeger UI:

  • • 打开浏览器并导航到:http://localhost:16686
  • • 从下拉菜单中选择一个服务
  • • 点击“Find Traces”查看分布式追踪

d2b5ca33bd20250103114005

UI 如图所示。我们可以追踪最终的 API 调用到它的发起位置。这就是 OpenTelemetry 的魅力所在。

故障排除

常见问题及解决方案:

  1. Jaeger 中没有追踪数据:
  • • 验证 OTLP 端点配置
  • • 检查收集器日志:docker-compose logs otel-collector
  • • 确保采样概率设置为 1.0
  1. 服务无法通信:
  • • 验证 docker-compose 中的网络配置
  • • 检查服务端口和 URL
  • • 检查服务日志:docker-compose logs service-name
  1. 追踪上下文未传播:
  • • 确保 RestTemplate 配置正确
  • • 验证日志模式是否包含追踪 ID
  • • 检查 OpenTelemetry 插桩设置

最佳实践

采样策略:

  • • 在开发环境中使用 1.0
  • • 根据流量调整生产环境中的采样率

日志记录:

  • • 始终包含追踪 ID 和跨度 ID
  • • 使用一致的日志级别
  • • 在日志中添加有意义的上下文

资源属性:

  • • 为追踪标记环境
  • • 添加服务版本
  • • 包含部署信息

错误处理:

  • • 正确传播和记录错误
  • • 在追踪中包含错误上下文
  • • 使用适当的 HTTP 状态码

结论

分布式追踪是一种变革性的方法,用于监控微服务并理清复杂的请求流。OpenTelemetry 是一个多功能、与供应商无关的解决方案,提供了跨分布式系统的可观测性。通过将 OpenTelemetry 集成到 Spring Boot 3 应用程序中,我们可以实现端到端的追踪,从而简化调试和性能监控。借助 Jaeger 和 OpenTelemetry 收集器等工具,开发者可以更有效地可视化追踪并优化微服务。这种设置确保了可扩展且可维护的可观测性方法,这对于现代应用程序至关重要。

没有可观测性的代码就像在黑暗中航行。有了 OpenTelemetry,您不仅是在编写代码,更是在构建一个您可以理解、调试和改进的系统。祝您编码愉快,愿您的追踪永远富有洞察力!🚀📊

请登录后发表评论

    没有回复内容