Java中的可观察性(Observability)平台构建:Metrics/Traces/Logs统一处理

Java中的可观察性(Observability)平台构建:Metrics/Traces/Logs统一处理

大家好,今天我们来深入探讨如何在Java中构建一个可观察性平台,实现Metrics、Traces和Logs的统一处理。可观察性对于现代微服务架构至关重要,它能帮助我们了解系统的内部状态,快速定位问题,优化性能,并确保系统的稳定运行。

1. 可观察性的三大支柱:Metrics, Traces, Logs

在开始构建平台之前,让我们先了解可观察性的三大支柱:

  • Metrics (指标): 数值型的数据,通常用于监控系统的性能和资源利用率。 例如,CPU使用率、内存占用、请求延迟、错误率等。 Metrics 是时间序列数据,可以进行聚合、计算和可视化,用于趋势分析和告警。

  • Traces (追踪): 记录一个请求在系统中各个服务之间传递的路径和耗时。 Traces 帮助我们了解请求的生命周期,识别瓶颈,并诊断分布式系统中的性能问题。 一个 Trace 由多个 Span 组成,每个 Span 代表一个服务中的一个操作。

  • Logs (日志): 文本形式的事件记录,包含程序运行时的信息,例如错误、警告、调试信息等。 Logs 可以帮助我们了解系统的行为,诊断问题,并进行审计。

这三个支柱相互补充,共同构成了一个完整的可观察性体系。 Metrics 提供宏观的系统状态概览,Traces 帮助我们追踪请求的路径,Logs 提供详细的事件信息。

支柱 类型 描述 用途
Metrics 数值型 指标数据,例如CPU使用率、内存占用、请求延迟、错误率等。 监控系统性能、资源利用率,趋势分析,告警。
Traces 结构化 记录一个请求在系统中各个服务之间传递的路径和耗时。 一个Trace由多个Span组成。 了解请求的生命周期,识别瓶颈,诊断分布式系统中的性能问题。
Logs 文本型 文本形式的事件记录,包含程序运行时的信息,例如错误、警告、调试信息等。 了解系统的行为,诊断问题,进行审计。

2. 选择合适的工具和技术栈

构建可观察性平台需要选择合适的工具和技术栈。 以下是一些常用的选项:

  • Metrics:

    • Micrometer: Java 生态系统中流行的指标收集库,可以与多种监控系统集成。
    • Prometheus: 开源的时间序列数据库,用于存储和查询指标数据。
    • Grafana: 开源的数据可视化工具,用于创建仪表盘和告警。
  • Traces:

    • OpenTelemetry: 开源的可观察性框架,提供统一的 API 和 SDK,用于收集、处理和导出 Traces、Metrics 和 Logs。
    • Jaeger: 开源的分布式追踪系统,用于存储和查询 Traces。
    • Zipkin: 另一个流行的开源分布式追踪系统。
  • Logs:

    • SLF4J: Java 的日志门面,允许在运行时选择不同的日志实现。
    • Logback: 流行的日志实现,可以配置日志格式、输出目标等。
    • ELK Stack (Elasticsearch, Logstash, Kibana): 流行的日志管理平台,用于收集、存储、分析和可视化 Logs。

我们今天将使用以下组合进行演示:

  • Metrics: Micrometer + Prometheus + Grafana
  • Traces: OpenTelemetry + Jaeger
  • Logs: SLF4J + Logback + ELK Stack (简化版,直接输出到控制台)

3. Metrics 的实现

首先,我们来演示如何使用 Micrometer 和 Prometheus 来收集和展示 Metrics。

3.1 添加依赖

pom.xml 文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-core</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>

3.2 代码实现

创建一个简单的 Spring Boot 应用,并添加一个 Controller 来暴露 Metrics。

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MetricsController {

    private final Counter requestCounter;

    @Autowired
    public MetricsController(MeterRegistry registry) {
        this.requestCounter = registry.counter("my_app_requests_total", "endpoint", "/hello");
    }

    @GetMapping("/hello")
    public String hello() {
        requestCounter.increment();
        return "Hello, World!";
    }
}

3.3 配置 Prometheus

application.propertiesapplication.yml 文件中配置 Prometheus 端点:

management.endpoints.web.exposure.include=prometheus
management.metrics.export.prometheus.enabled=true

3.4 启动应用并访问 Prometheus 端点

启动 Spring Boot 应用,然后访问 http://localhost:8080/actuator/prometheus。 您应该能够看到 Prometheus 格式的 Metrics 数据,其中包含 my_app_requests_total 指标。

3.5 使用 Grafana 可视化 Metrics

  1. 安装并启动 Grafana。
  2. 在 Grafana 中添加 Prometheus 数据源,指定 Prometheus 的地址(例如 http://localhost:9090,如果 Prometheus 运行在本地默认端口)。
  3. 创建一个新的仪表盘,并添加一个面板。
  4. 在面板中选择 Prometheus 数据源,并输入查询语句 my_app_requests_total
  5. 选择合适的图表类型,例如折线图或柱状图。
  6. 保存仪表盘。

现在,您就可以在 Grafana 中看到 my_app_requests_total 指标的实时数据和历史趋势。

4. Traces 的实现

接下来,我们来演示如何使用 OpenTelemetry 和 Jaeger 来收集和展示 Traces。

4.1 添加依赖

pom.xml 文件中添加以下依赖:

<dependencies>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-api</artifactId>
        <version>1.35.0</version>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-sdk</artifactId>
        <version>1.35.0</version>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-jaeger</artifactId>
        <version>1.35.0</version>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-extension-annotations</artifactId>
        <version>1.35.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

4.2 代码实现

创建一个简单的 Spring Boot 应用,并添加一个 Interceptor 来拦截请求并创建 Span。

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class OpenTelemetryInterceptor implements HandlerInterceptor {

    private final Tracer tracer = GlobalOpenTelemetry.get().getTracer("my-app", "1.0.0");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Span span = tracer.spanBuilder(request.getRequestURI()).startSpan();
        request.setAttribute("span", span);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        Span span = (Span) request.getAttribute("span");
        if (span != null) {
            span.end();
        }
    }
}

注册 Interceptor

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private OpenTelemetryInterceptor openTelemetryInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(openTelemetryInterceptor);
    }
}

修改 HelloController

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    private final Tracer tracer = GlobalOpenTelemetry.get().getTracer("my-app", "1.0.0");

    @GetMapping("/hello")
    public String hello() {
        Span span = tracer.spanBuilder("hello-logic").startSpan();
        try {
            // some logic
            Thread.sleep(100);
            return "Hello, World!";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            span.end();
        }
    }
}

4.3 配置 OpenTelemetry

我们需要配置 OpenTelemetry SDK,以便将 Traces 导出到 Jaeger。 这可以通过编程方式或通过环境变量来实现。 这里我们使用编程方式:

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class OpenTelemetryConfig {

    @Value("${jaeger.host:localhost}")
    private String jaegerHost;

    @Value("${jaeger.port:4317}")
    private int jaegerPort;

    @PostConstruct
    public void setupOpenTelemetry() {
        Resource resource = Resource.getDefault().merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "my-app")));

        JaegerGrpcSpanExporter jaegerExporter = JaegerGrpcSpanExporter.builder()
                .setEndpoint(jaegerHost + ":" + jaegerPort)
                .setTimeout(30, TimeUnit.SECONDS)
                .build();

        SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(jaegerExporter).build())
                .setResource(resource)
                .build();

        OpenTelemetrySdk.builder()
                .setTracerProvider(sdkTracerProvider)
                .buildAndRegisterGlobal();
    }
}

4.4 启动 Jaeger

使用 Docker 启动 Jaeger:

docker run -d -p 16686:16686 -p 4317:4317 jaegertracing/all-in-one:latest

4.5 启动应用并访问 /hello 端点

启动 Spring Boot 应用,然后访问 http://localhost:8080/hello

4.6 使用 Jaeger UI 查看 Traces

访问 Jaeger UI:http://localhost:16686。 您应该能够看到 my-app 服务的 Traces,包括 /hello 端点的请求路径和耗时。

5. Logs 的实现

最后,我们来演示如何使用 SLF4J 和 Logback 来记录 Logs。

5.1 添加依赖

如果使用 Spring Boot,默认已经包含了 SLF4J 和 Logback 的依赖。

5.2 代码实现

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoggingController {

    private static final Logger logger = LoggerFactory.getLogger(LoggingController.class);

    @GetMapping("/log")
    public String log() {
        logger.info("This is an info log message.");
        logger.warn("This is a warn log message.");
        logger.error("This is an error log message.");
        return "Logs generated!";
    }
}

5.3 配置 Logback

创建一个 logback.xml 文件来配置日志格式和输出目标。 例如,将日志输出到控制台:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

5.4 启动应用并访问 /log 端点

启动 Spring Boot 应用,然后访问 http://localhost:8080/log。 您应该能够在控制台中看到生成的日志信息。

6. 统一处理:关联 Metrics, Traces 和 Logs

为了实现可观察性的统一处理,我们需要将 Metrics, Traces 和 Logs 关联起来。 这可以通过以下方式实现:

  • 使用 Trace ID: 在 Logs 中记录 Trace ID,这样我们可以通过 Trace ID 将 Logs 与 Traces 关联起来。 OpenTelemetry 会自动生成 Trace ID,我们可以通过 OpenTelemetry API 获取 Trace ID,并将其添加到 Logback 的 MDC (Mapped Diagnostic Context) 中。

  • 使用 Tags/Labels: 在 Metrics 中添加 Tags/Labels,用于标识请求的来源、目标、状态等信息。 这些 Tags/Labels 可以与 Traces 和 Logs 中的信息对应,从而实现数据的关联。

6.1 在 Logs 中记录 Trace ID

修改 logback.xml 文件,添加 Trace ID 到日志格式中:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

创建 TraceId 的 MDC

import io.opentelemetry.api.trace.Span;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class TraceIdMDCAwareInterceptor implements HandlerInterceptor {

    private static final String TRACE_ID_KEY = "traceId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Span span = (Span) request.getAttribute("span");
        if (span != null) {
            MDC.put(TRACE_ID_KEY, span.getSpanContext().getTraceId());
        } else {
            MDC.put(TRACE_ID_KEY, "N/A");
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        MDC.remove(TRACE_ID_KEY);
    }
}

注册 Interceptor

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private OpenTelemetryInterceptor openTelemetryInterceptor;

    @Autowired
    private TraceIdMDCAwareInterceptor traceIdMDCAwareInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(openTelemetryInterceptor);
        registry.addInterceptor(traceIdMDCAwareInterceptor);
    }
}

现在,当您访问 /log 端点时,您应该能够在控制台中看到包含 Trace ID 的日志信息。 通过 Trace ID,您可以在 Jaeger 中找到对应的 Trace,并查看该请求的详细信息。

6.2 在 Metrics 中添加 Tags/Labels

修改 MetricsController ,添加 endpoint tag

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MetricsController {

    private final Counter requestCounter;

    @Autowired
    public MetricsController(MeterRegistry registry) {
        this.requestCounter = registry.counter("my_app_requests_total", "endpoint", "/hello");
    }

    @GetMapping("/hello")
    public String hello() {
        requestCounter.increment();
        return "Hello, World!";
    }
}

7. 总结与展望

构建一个完整的可观察性平台需要考虑多个方面,包括选择合适的工具和技术栈,实现 Metrics, Traces 和 Logs 的收集和展示,以及将这些数据关联起来。 通过本文的演示,您已经了解了如何使用 Micrometer, Prometheus, Grafana, OpenTelemetry, Jaeger, SLF4J 和 Logback 来构建一个简单的可观察性平台。

  • 重点回顾: 介绍了可观察性的三大支柱,并演示了如何使用常见的工具和技术栈来实现 Metrics, Traces 和 Logs 的收集和展示。
  • 统一处理: 讲解了如何通过 Trace ID 和 Tags/Labels 将 Metrics, Traces 和 Logs 关联起来,实现可观察性的统一处理。

下一步,您可以考虑将这些技术应用到您的实际项目中,并根据您的需求进行定制和扩展。 例如,您可以:

  • 使用更高级的日志分析工具,例如 ELK Stack,来分析和可视化 Logs。
  • 添加更多的 Metrics 和 Traces,以便更全面地了解系统的状态。
  • 实现自动化的告警和故障诊断,以便快速响应问题。

希望今天的分享对您有所帮助! 谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注