Java `Observability` `Metrics` (`Micrometer`), `Logging` (`Logback`, `Log4j2`), `Tracing`

各位观众老爷,大家好!今天咱们来聊聊Java应用的“透视眼”——Observability(可观测性)。这年头,光把程序跑起来还不够,还得知道它在干啥,有没有偷偷摸摸出幺蛾子,对吧?Observability就是帮你监控、诊断和理解你的应用,让它的一切尽在掌握。

我们今天主要围绕 Metrics (Micrometer), Logging (Logback, Log4j2), 和 Tracing 这三个方面展开。

一、 Metrics:给应用做个体检

想象一下,你去医院体检,医生会量血压、测心跳、验血等等。Metrics就是给你的应用做类似的事情,它收集各种指标,比如CPU使用率、内存占用、请求响应时间、数据库连接数等等。这些指标就像应用的健康报告,告诉你它是否健康。

Micrometer 是一个Java指标收集的Facade,它类似于SLF4J之于日志,你只需要使用Micrometer的API来埋点,然后通过配置,就可以将指标数据导出到各种监控系统,比如Prometheus, Graphite, Datadog, Azure Monitor等。

  1. 引入依赖:

首先,我们需要在项目中引入Micrometer的依赖。以Maven为例:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
    <version>1.12.3</version> <!-- 请使用最新版本 -->
</dependency>

<!-- 选择你需要的监控系统适配器,比如Prometheus -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.12.3</version> <!-- 请使用最新版本 -->
</dependency>
  1. 配置 Metrics Registry:

你需要创建一个 MeterRegistry 实例,它负责收集和导出指标数据。

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;

public class MetricsConfig {

    public static MeterRegistry registry() {
        PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
        return prometheusRegistry;
    }
}
  1. 埋点:

接下来,就可以在代码中埋点,收集你关心的指标。

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.MeterRegistry;

import java.util.Random;
import java.util.concurrent.TimeUnit;

public class MyService {

    private final Counter requestCounter;
    private final Timer processTimer;
    private final MeterRegistry registry;

    public MyService(MeterRegistry registry) {
        this.registry = registry;
        this.requestCounter = Counter.builder("my_service.requests")
                .description("Number of requests to my service")
                .register(registry);

        this.processTimer = Timer.builder("my_service.process_time")
                .description("Time taken to process a request")
                .register(registry);
    }

    public String processRequest(String request) {
        requestCounter.increment();

        return processTimer.record(() -> {
            // 模拟一些耗时操作
            try {
                Thread.sleep(new Random().nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Processed: " + request;
        });
    }
}

在这个例子中:

  • Counter 用于记录请求的数量。
  • Timer 用于记录请求的处理时间。
  • registry 是我们创建的 MeterRegistry 实例。
  1. 暴露 Metrics 端点:

如果使用 Prometheus,你需要暴露一个端点,让 Prometheus 可以抓取指标数据。

import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MetricsController {

    private final PrometheusMeterRegistry registry;

    public MetricsController(PrometheusMeterRegistry registry) {
        this.registry = registry;
    }

    @GetMapping("/prometheus")
    public String prometheus() {
        return registry.scrape();
    }
}
  1. 启动应用,并访问 /prometheus 端点

你就可以看到Prometheus格式的metrics数据了。

Metrics 类型总结:

类型 描述 例子
Counter 计数器,用于记录某个事件发生的次数。只能增加,不能减少。 请求的数量、错误的数量、用户注册的数量
Gauge 仪表盘,用于记录某个指标的当前值。可以随时变化。 CPU 使用率、内存占用、队列长度
Timer 计时器,用于记录某个操作的耗时。可以计算平均值、最大值、最小值、中位数等。 请求的响应时间、数据库查询时间、任务执行时间
DistributionSummary 分布式摘要,用于记录某个事件的分布情况。可以计算平均值、最大值、最小值、分位数等。类似于Timer,但更通用,可以用于记录任意数值的分布情况,而不仅限于时间。 包大小、消息长度
LongTaskTimer 长任务计时器,用于记录长时间运行的任务的耗时。与 Timer 的区别在于,LongTaskTimer 会记录任务的活跃状态,可以知道当前有多少任务正在运行,以及它们已经运行了多长时间。 定时任务、批处理任务

二、 Logging:应用的日记本

Logging就像应用的日记本,它记录了应用运行过程中的各种事件和信息。通过分析日志,我们可以了解应用的运行状态、发现问题、进行调试等等。

Java中常用的日志框架有 Logback 和 Log4j2。它们都提供了强大的日志功能,包括日志级别、日志格式、日志输出目标等等。

  1. 引入依赖:

以Logback为例:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.5.3</version> <!-- 请使用最新版本 -->
</dependency>

对于Log4j2:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.23.0</version> <!-- 请使用最新版本 -->
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.23.0</version> <!-- 请使用最新版本 -->
</dependency>
  1. 配置日志:

Logback的配置文件是 logback.xml,Log4j2的配置文件是 log4j2.xml。你可以在配置文件中定义日志级别、日志格式、日志输出目标等等。

Logback配置示例 (logback.xml):

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

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Log4j2配置示例 (log4j2.xml):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>
  1. 使用日志:

在代码中使用日志非常简单。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyService {

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

    public String processRequest(String request) {
        logger.info("Processing request: {}", request);
        try {
            // ...
            return "Processed: " + request;
        } catch (Exception e) {
            logger.error("Error processing request: {}", request, e);
            throw e;
        }
    }
}

在这个例子中:

  • 我们首先获取一个 Logger 实例。
  • 然后使用 logger.info() 记录信息级别的日志。
  • 使用 logger.error() 记录错误级别的日志。

日志级别总结:

级别 描述
TRACE 最详细的日志,通常用于调试。
DEBUG 调试信息,用于开发环境。
INFO 普通信息,用于记录应用运行状态。
WARN 警告信息,表示可能存在问题。
ERROR 错误信息,表示发生了错误。
FATAL 致命错误,表示应用无法继续运行。

三、 Tracing:应用的足迹

Tracing就像给你的应用装上了GPS,它可以追踪请求在不同服务之间的调用链,帮助你分析性能瓶颈、定位问题。

Tracing 通常使用 OpenTelemetry 或者 Zipkin、Jaeger 等工具来实现。OpenTelemetry正在成为行业标准。

  1. 引入依赖:

这里以 OpenTelemetry 为例,首先引入必要的依赖:

<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-zipkin</artifactId>
    <version>1.35.0</version> <!-- 请使用最新版本 -->
</dependency>

<!-- 如果使用 Spring Boot,可以考虑使用 Spring Cloud Sleuth 集成 OpenTelemetry -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    <version>4.1.0</version> <!-- 请使用最新版本 -->
</dependency>
  1. 配置 OpenTelemetry:

你需要配置 OpenTelemetry SDK,并将其指向一个 Tracing 后端,比如 Zipkin。

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Tracer;
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.sdk.trace.samplers.Sampler;
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;

import java.io.IOException;
import java.util.Properties;

public class TracingConfig {

    public static OpenTelemetry initOpenTelemetry(String serviceName, String zipkinUrl) throws IOException {
        // Create a new Zipkin span exporter.
        ZipkinSpanExporter zipkinExporter = ZipkinSpanExporter.builder()
            .setEndpoint(zipkinUrl)
            .build();

        Resource serviceNameResource = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, serviceName));

        // Set to process the spans in batches for efficiency.
        SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
            .addSpanProcessor(BatchSpanProcessor.builder(zipkinExporter).build())
            .setResource(Resource.getDefault().merge(serviceNameResource))
            .setSampler(Sampler.alwaysOn()) //Always sample for this example
            .build();

        OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
            .setTracerProvider(tracerProvider)
            .buildAndRegisterGlobal();

        Runtime.getRuntime().addShutdownHook(new Thread(tracerProvider::close));

        return openTelemetry;
    }

    public static Tracer getTracer(OpenTelemetry openTelemetry, String instrumentationScopeName, String instrumentationScopeVersion) {
        return openTelemetry.getTracer(instrumentationScopeName, instrumentationScopeVersion);
    }
}
  1. 埋点:

在代码中埋点,记录请求的开始和结束,以及其他你关心的信息。

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;

public class MyService {

    private final Tracer tracer;

    public MyService(Tracer tracer) {
        this.tracer = tracer;
    }

    public String processRequest(String request) {
        Span span = tracer.spanBuilder("processRequest").startSpan();
        try (var scope = span.makeCurrent()) {
            span.setAttribute("request", request);
            // ...
            String result = "Processed: " + request;
            span.setAttribute("result", result);
            return result;
        } catch (Exception e) {
            span.recordException(e);
            throw e;
        } finally {
            span.end();
        }
    }
}

在这个例子中:

  • 我们首先获取一个 Tracer 实例。
  • 然后使用 tracer.spanBuilder() 创建一个 Span,Span 代表一个请求的生命周期。
  • 使用 span.setAttribute() 添加属性,记录请求的详细信息。
  • 使用 span.recordException() 记录异常信息。
  • 最后使用 span.end() 结束 Span。
  1. 查看 Tracing 数据:

启动 Zipkin 或者 Jaeger,你就可以在界面上看到请求的调用链,以及每个服务的耗时等信息。

Tracing 概念总结:

概念 描述
Trace 一个 Trace 代表一个完整的请求调用链。它由多个 Span 组成。
Span 一个 Span 代表一个请求中的一个操作。比如,一个服务调用、一个数据库查询等等。
Context Context 用于在不同的服务之间传递 Trace 信息。
Baggage Baggage 类似于 Context,但它可以传递任意的键值对。
Sampler Sampler 用于控制哪些请求需要被 Tracing。

四、 总结

  • Metrics 告诉你应用的状态,就像体检报告。
  • Logging 记录应用的运行轨迹,就像日记本。
  • Tracing 追踪请求的调用链,就像GPS。

通过 Metrics、Logging 和 Tracing,你可以全方位地了解你的应用,及时发现问题、解决问题,让你的应用更加健康、稳定。

一些建议:

  • 尽早开始: 不要等到出现问题才开始考虑 Observability。
  • 选择合适的工具: 根据你的需求和预算,选择合适的 Metrics、Logging 和 Tracing 工具。
  • 标准化: 统一 Metrics 的命名规范、Logging 的格式、Tracing 的埋点方式。
  • 自动化: 尽可能自动化 Metrics 的收集、Logging 的分析、Tracing 数据的处理。
  • 持续改进: 不断优化你的 Observability 系统,让它更好地服务于你的应用。

好了,今天的讲座就到这里。希望大家有所收获,以后开发应用的时候,记得给它装上“透视眼”哦! 谢谢大家!

发表回复

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