传统监控只有指标 (Metrics)——CPU、内存、请求数等数值,但这只能告诉我们系统「现在有问题」,却很难回答「为什么有问题」。为了解决这个痛点,可观测性引入了三个核心信号:指标 (Metrics)、日志 (Logging)、追踪 (Tracing)。三者相互补充,共同构成了现代可观测性体系。
| 支柱 | 数据类型 | 典型工具 | 主要用途 |
|---|---|---|---|
| Metrics | 数值型时间序列 | Prometheus, Zabbix | 趋势分析、告警、容量规划 |
| Logging | 文本型事件记录 | ELK, Loki | 故障排查、审计、业务分析 |
| Tracing | 请求链路数据 | Jaeger, Zipkin | 分布式调用分析、性能瓶颈定位 |
# Metrics 告警:检测到高延迟
alert: 请求延迟超过 500ms
for: 5m
annotations: 告警 ID: "high_latency"
# Logging 定位:查看具体日志
grep "high_latency" /var/log/app.log
# 输出: [ERROR] 请求 12345 处理耗时 1200ms,DB 超时
# Tracing 追踪:找出具体瓶颈
jaeger 查询 trace_id = 12345
# 显示: API → 订单服务 (600ms) → 数据库 (500ms) → 返回
Prometheus 是受 Google 内部监控系统 Borgmon 启发而开发的。它的核心设计理念是「基于拉取的监控模型」和「多维数据模型」。与传统的基于推的监控系统(如 Zabbix)不同,Prometheus 主动从目标端拉取指标,通过标签 (Label) 实现多维度的查询和分析。
数据模型:
PromQL 核心操作:
# 查询 CPU 使用率 (百分比)
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 查询 95 分位延迟
histogram_quantile(0.95, sum by(le) (http_request_duration_seconds_bucket))
# 告警规则: 请求错误率超过 5%
groups:
- name: http_errors
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
for: 10m
labels:
severity: critical
时序数据具有写多读少、数据点持续增加、时间顺序写入等特点。传统关系数据库的 B+ 树索引机制在大量写入时性能较差,且存储膨胀严重。时序数据库 (TSDB) 专为时序数据优化,通过列式存储、压缩技术、时间分区等设计,实现了高效写入和查询。
写入流程: 数据先写入内存中的 MemTable,定期刷写到磁盘的 SSTable(Sorted String Table)。通过 Compaction 合并和清理删除的数据。
压缩技术: Gorilla 压缩利用 XOR 编码存储浮点数变化,对时间戳使用 Delta-Delta 编码。
// Gorilla XOR 编码 (浮点数压缩)
double previous_value;
uint64_t prev_xor = 0;
void compress_value(double value) {
uint64_t current_bits = float_to_bits(value);
uint64_t xor = current_bits ^ prev_xor;
if (xor == 0) {
// 值完全一样,只需写一个控制位
write_bit('0');
} else {
write_bit('1');
int leading_zeros = count_leading_zeros(xor);
int trailing_zeros = count_trailing_zeros(xor);
// 使用前导零和后导零信息压缩 XOR 值
encode_xor_with_zeros(xor, leading_zeros, trailing_zeros);
}
prev_xor = current_bits;
}
传统日志分析使用 grep 和 awk 在单机上进行,当服务器数量增加、日志量达到 TB 级别时,这种方式已经无法满足需求。ELK 架构(Elasticsearch + Logstash + Kibana)提供了一套分布式日志采集、存储、搜索、可视化的完整方案。
Logstash 流水线: 输入 → 过滤器 → 输出。通过 grok 正则解析非结构化日志,mutate 转换字段,date 解析时间戳。
Elasticsearch 索引: 文档 → 倒排索引 → 搜索。支持 term、match、range 等查询,支持聚合分析。
# Logstash 流水线配置 (解析 Nginx 日志)
input {
beats {
port => 5044
}
}
filter {
grok {
match => { "message" => "%{IP:client_ip} - - [%{HTTPDATE:timestamp}] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}\" %{NUMBER:status} %{NUMBER:bytes}" }
}
date {
match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
}
useragent {
source => "http_user_agent"
target => "user_agent"
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
index => "nginx_logs_%{+YYYY.MM.dd}"
}
}
在微服务架构中,一个请求可能跨越多个服务、多个进程。传统的日志和指标无法将同一个请求在不同服务中的行为关联起来。分布式链路追踪通过在请求中传递上下文 (Context),将请求的完整执行路径串联起来。
核心数据结构:
Context Propagation: 通过 HTTP Headers(如 traceparent)或 gRPC Metadata 传递 Trace 上下文。
# 使用 OpenTelemetry 创建 Span
from opentelemetry import trace
from opentelemetry.trace import TracerProvider
# 初始化 tracer
tracer = trace.get_tracer("my-service")
def handle_request(request):
# 创建一个新的 Span,作为根 Span
with tracer.start_as_current_span("handle_request") as span:
span.set_attribute("http.method", request.method)
span.set_attribute("http.url", request.url)
# 调用下游服务 (自动传递 Trace Context)
with tracer.start_as_current_span("call_service_b") as child_span:
call_service_b(request)
child_span.set_attribute("service.b.status", "success")
# 记录异常 (如果有)
span.record_exception(exception)
span.set_status(trace.Status(trace.StatusCode.ERROR, "DB error"))
# 传播 Trace Context 到 HTTP 请求头
# 在 outgoing HTTP 请求中注入 traceparent 头
headers = {}
propagator.inject(headers)
requests.get("http://service-b/api", headers=headers)