可观测性模式:日志、指标、链路追踪
说实话,可观测性这个词,前几年还只是个概念。
但到了今天,谁要是没在系统里搭过一套完整的可观测体系,那基本等于在裸奔。我见过太多线上事故,明明系统挂了,却连根毛都查不到——日志没打、指标没采、链路没连,最后只能靠猜。嗯,猜出来的结论,十有八九是错的。
可观测性说白了就三件事:日志(Logs)、指标(Metrics)、链路追踪(Traces)。这三者合起来,业界叫它“三大支柱”。今天我们就一个一个拆开讲,再聊聊怎么用ELK、Prometheus、Jaeger把它们落地。
1. 日志(Logs)——最原始,也最容易被忽视
日志是最古老的可观测手段。我刚开始写Java那会儿,System.out.println就是我的调试神器。但到了生产环境,这么干就是找死。
日志的关键在于结构化和集中化。你想想看,几百台机器,每台都写本地文件,出问题了你一台台去翻?不现实。
我个人习惯用ELK(Elasticsearch + Logstash + Kibana)这套组合。Logstash负责采集和解析,Elasticsearch负责存储和搜索,Kibana负责展示。
这里有个避坑指南:我曾经在一个项目里,日志量一天冲到几个TB,Elasticsearch直接OOM。后来加了日志采样和生命周期管理,才稳住。所以,日志一定要设保留期限和采样率,别什么都往里塞。
// Java示例:使用Logback输出结构化JSON日志
import ch.qos.logback.classic.Logger;
import net.logstash.logback.argument.StructuredArguments;
Logger logger = (Logger) LoggerFactory.getLogger("MyService");
logger.info("用户下单成功",
StructuredArguments.keyValue("userId", 12345),
StructuredArguments.keyValue("orderId", "ORD-20231001-001"),
StructuredArguments.keyValue("amount", 299.99)
);
// 输出:{"message":"用户下单成功","userId":12345,"orderId":"ORD-20231001-001","amount":299.99}
// C++示例:使用spdlog输出结构化日志
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
auto logger = spdlog::rotating_logger_mt("file_logger", "logs/app.log", 1024*1024*10, 3);
logger->set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");
logger->info("用户下单成功, userId={}, orderId={}, amount={}", 12345, "ORD-20231001-001", 299.99);
2. 指标(Metrics)——系统的“体温计”
指标和日志不同。日志是离散的事件,指标是连续的数值。你想想看,体温计不会告诉你“你几点几分咳嗽了”,它只告诉你“你现在38.5度”。
指标最常用的工具是Prometheus。它采用拉模型,定期从你的服务端点抓取数据。配合Grafana做可视化,基本是标配。
常见的指标类型有四种:
| 类型 | 说明 | 示例 |
|---|---|---|
| Counter(计数器) | 只增不减,用于累计值 | 请求总数、错误总数 |
| Gauge(仪表盘) | 可增可减,用于瞬时值 | 当前内存使用量、在线用户数 |
| Histogram(直方图) | 统计分布,用于延迟等 | 请求延迟的P50、P99 |
| Summary(摘要) | 类似直方图,但可计算分位数 | 请求延迟的滑动窗口分位数 |
我在项目中遇到过一个问题:Prometheus的拉模式在Kubernetes环境下非常方便,但如果你服务实例频繁重启,可能会丢失短暂的数据点。这时候可以配合Pushgateway做补充,但要注意别滥用。
// Java示例:使用Micrometer暴露Prometheus指标
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
public class OrderService {
private final Counter orderCounter;
public OrderService(MeterRegistry registry) {
this.orderCounter = Counter.builder("orders.created")
.description("已创建的订单总数")
.register(registry);
}
public void createOrder() {
// 业务逻辑...
orderCounter.increment();
}
}
// C++示例:使用Prometheus-cpp暴露指标
#include <prometheus/counter.h>
#include <prometheus/registry.h>
auto registry = std::make_shared<prometheus::Registry>();
auto& counter_family = prometheus::BuildCounter()
.Name("orders_created_total")
.Help("已创建的订单总数")
.Register(*registry);
auto& counter = counter_family.Add({});
// 业务逻辑中调用
counter.Increment();
3. 链路追踪(Traces)——跨服务的“侦探”
微服务架构下,一个请求可能要经过十几个服务。日志和指标只能告诉你“哪个服务慢了”,但链路追踪能告诉你“慢在哪一步”。
链路追踪的核心是Trace ID和Span ID。一个Trace代表一次完整的请求,每个Span代表请求中的一个步骤。Span之间通过Parent-Child关系串联。
我常用的工具是Jaeger,它和OpenTelemetry协议配合得很好。你只需要在代码里埋点,Jaeger会自动收集并展示调用拓扑。
// Java示例:使用OpenTelemetry进行链路追踪
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
Tracer tracer = openTelemetry.getTracer("order-service");
Span span = tracer.spanBuilder("createOrder").startSpan();
try (Scope scope = span.makeCurrent()) {
// 业务逻辑...
span.setAttribute("orderId", "ORD-20231001-001");
} finally {
span.end();
}
// C++示例:使用OpenTelemetry C++ SDK
#include <opentelemetry/trace/span.h>
#include <opentelemetry/trace/tracer.h>
auto tracer = opentelemetry::trace::TracerProvider::GetTracer("order-service");
auto span = tracer->StartSpan("createOrder");
auto scope = tracer->WithActiveSpan(span);
// 业务逻辑...
span->SetAttribute("orderId", "ORD-20231001-001");
span->End();
4. 三大支柱如何协同工作?
日志、指标、链路追踪不是孤立的。它们应该互相补充,形成闭环。
举个例子:
- Prometheus告警:订单服务响应时间P99超过2秒。
- 你打开Jaeger,找到对应时间段的慢Trace,发现瓶颈在“库存服务”的某个Span。
- 你再去ELK里搜这个Trace ID对应的日志,发现库存服务抛了一个数据库连接超时异常。
- 问题定位:数据库连接池太小。
你看,三个工具缺一不可。没有指标,你发现不了问题;没有链路,你定位不到服务;没有日志,你找不到根因。
5. 整体架构图
下面这张图展示了日志、指标、链路追踪三者的数据流和工具链关系:
6. 落地建议
最后,我分享几个实战中的经验:
- 不要一步到位。 先上日志,再上指标,最后上链路。一口吃不成胖子。
- 统一标准。 日志格式、指标命名、Trace ID传递方式,团队内部一定要统一。否则后期维护成本极高。
- 关注成本。 可观测性工具本身也是资源消耗大户。ELK的磁盘、Prometheus的内存、Jaeger的网络,都要提前规划。
- 自动化告警。 光有数据没用,得配上告警规则。我习惯用Prometheus的Alertmanager,配合钉钉或企业微信通知。