各位观众老爷,大家好!今天咱们来聊聊Java应用的“透视眼”——Observability(可观测性)。这年头,光把程序跑起来还不够,还得知道它在干啥,有没有偷偷摸摸出幺蛾子,对吧?Observability就是帮你监控、诊断和理解你的应用,让它的一切尽在掌握。
我们今天主要围绕 Metrics (Micrometer), Logging (Logback, Log4j2), 和 Tracing 这三个方面展开。
一、 Metrics:给应用做个体检
想象一下,你去医院体检,医生会量血压、测心跳、验血等等。Metrics就是给你的应用做类似的事情,它收集各种指标,比如CPU使用率、内存占用、请求响应时间、数据库连接数等等。这些指标就像应用的健康报告,告诉你它是否健康。
Micrometer 是一个Java指标收集的Facade,它类似于SLF4J之于日志,你只需要使用Micrometer的API来埋点,然后通过配置,就可以将指标数据导出到各种监控系统,比如Prometheus, Graphite, Datadog, Azure Monitor等。
- 引入依赖:
首先,我们需要在项目中引入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>
- 配置 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;
}
}
- 埋点:
接下来,就可以在代码中埋点,收集你关心的指标。
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
实例。
- 暴露 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();
}
}
- 启动应用,并访问
/prometheus
端点
你就可以看到Prometheus格式的metrics数据了。
Metrics 类型总结:
类型 | 描述 | 例子 |
---|---|---|
Counter | 计数器,用于记录某个事件发生的次数。只能增加,不能减少。 | 请求的数量、错误的数量、用户注册的数量 |
Gauge | 仪表盘,用于记录某个指标的当前值。可以随时变化。 | CPU 使用率、内存占用、队列长度 |
Timer | 计时器,用于记录某个操作的耗时。可以计算平均值、最大值、最小值、中位数等。 | 请求的响应时间、数据库查询时间、任务执行时间 |
DistributionSummary | 分布式摘要,用于记录某个事件的分布情况。可以计算平均值、最大值、最小值、分位数等。类似于Timer,但更通用,可以用于记录任意数值的分布情况,而不仅限于时间。 | 包大小、消息长度 |
LongTaskTimer | 长任务计时器,用于记录长时间运行的任务的耗时。与 Timer 的区别在于,LongTaskTimer 会记录任务的活跃状态,可以知道当前有多少任务正在运行,以及它们已经运行了多长时间。 | 定时任务、批处理任务 |
二、 Logging:应用的日记本
Logging就像应用的日记本,它记录了应用运行过程中的各种事件和信息。通过分析日志,我们可以了解应用的运行状态、发现问题、进行调试等等。
Java中常用的日志框架有 Logback 和 Log4j2。它们都提供了强大的日志功能,包括日志级别、日志格式、日志输出目标等等。
- 引入依赖:
以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>
- 配置日志:
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>
- 使用日志:
在代码中使用日志非常简单。
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正在成为行业标准。
- 引入依赖:
这里以 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>
- 配置 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);
}
}
- 埋点:
在代码中埋点,记录请求的开始和结束,以及其他你关心的信息。
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。
- 查看 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 系统,让它更好地服务于你的应用。
好了,今天的讲座就到这里。希望大家有所收获,以后开发应用的时候,记得给它装上“透视眼”哦! 谢谢大家!