2. 监控数据采集:日志、指标与链路追踪
监控数据采集,是整个告警系统的地基。地基没打好,上层再漂亮的告警规则也是空中楼阁。
我个人习惯把采集对象分成三类:日志、指标、链路追踪。这三者各有侧重,缺一不可。你想想看,日志告诉你「发生了什么」,指标告诉你「系统有多忙」,链路追踪告诉你「请求到底卡在哪了」。今天我们就逐个拆解。
2.1 日志采集:Filebeat 与 Logstash
日志是最传统的监控数据源。但说实话,很多团队的日志采集做得并不好——要么丢日志,要么采集器把业务进程给拖垮了。
2.1.1 Filebeat:轻量级日志采集器
Filebeat 是 Elastic 家的轻量级采集器。它有多轻?我记得有一次在 512MB 的机器上跑,内存占用不到 20MB。嗯,这就是它的核心优势——几乎不占资源。
它的工作流程很简单:
- 监听日志文件的变化
- 读取新增的日志行
- 发送到 Logstash 或 Elasticsearch
我在项目中遇到过一个问题:Filebeat 默认的 harvester_buffer_size 是 16384 字节。如果单行日志超过这个值,会被截断。当时排查了半天,最后发现是日志太长被切了。避坑指南:生产环境建议调大这个值。
核心配置示例:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/app/*.log
multiline.pattern: '^\d{4}-\d{2}-\d{2}'
multiline.negate: true
multiline.match: after
output.logstash:
hosts: ["logstash:5044"]
个人经验:多行日志(比如 Java 异常栈)一定要配置 multiline。否则一条异常会被拆成几十条日志,告警根本没法看。
2.1.2 Logstash:日志处理管道
Logstash 是日志处理的中枢。它接收 Filebeat 发来的数据,做解析、过滤、转换,最后输出到存储系统。
我曾经见过一个团队,把日志解析逻辑全写在应用代码里。结果每次改格式都要重新发布。其实用 Logstash 的 grok 插件就能搞定,完全不用动业务代码。
Logstash 的典型处理流程:
- 输入:从 Filebeat 接收日志
- 过滤:用 grok 解析日志结构,提取字段
- 输出:写入 Elasticsearch
过滤配置示例:
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}" }
}
date {
match => ["timestamp", "ISO8601"]
}
}
注意:Logstash 是 Java 写的,内存消耗不小。我建议至少给 2GB 堆内存。如果日志量特别大,可以考虑用 Kafka 做缓冲层,避免 Logstash 直接扛不住。
2.2 指标采集:Prometheus Exporter
指标采集,说白了就是定期抓取系统的数值状态。CPU 使用率、内存占用、QPS、延迟……这些都是指标。
Prometheus 是当前最主流的指标采集方案。它的核心思想是「拉模式」——Prometheus Server 定期去 Exporter 拉数据。
2.2.1 常用 Exporter
我整理了几个最常用的 Exporter:
| Exporter 名称 | 监控对象 | 默认端口 |
|---|---|---|
| node_exporter | 服务器硬件/OS 指标 | 9100 |
| blackbox_exporter | 网络探测(HTTP/ICMP/TCP) | 9115 |
| mysqld_exporter | MySQL 数据库指标 | 9104 |
| redis_exporter | Redis 指标 | 9121 |
我记得有一次线上 MySQL 慢查询突然暴增。DBA 查了半天没找到原因。我让他部署了 mysqld_exporter,一看 Grafana 面板——原来是连接数打满了,大量请求在排队。指标数据不会骗人。
2.2.2 自定义指标
有时候官方 Exporter 不够用,需要自己写。Prometheus 提供了多种语言的客户端库。
我个人习惯用 Python 写自定义 Exporter,因为快。下面是一个简单的示例:
from prometheus_client import start_http_server, Gauge
import time
import random
# 定义一个指标
request_latency = Gauge('app_request_latency_seconds', 'Request latency in seconds')
def collect_metrics():
while True:
# 模拟采集业务延迟
latency = random.uniform(0.1, 2.0)
request_latency.set(latency)
time.sleep(15)
if __name__ == '__main__':
start_http_server(8000)
collect_metrics()
避坑指南:我曾经把采集间隔设成 1 秒,结果 Prometheus 拉取太频繁,把业务接口打挂了。建议采集间隔至少 15 秒,Prometheus 的 scrape_interval 也设成 15 秒。
2.3 链路追踪:Jaeger
日志和指标能告诉你「系统出问题了」,但链路追踪能告诉你「问题出在哪一环」。尤其在微服务架构下,一个请求可能经过十几个服务,没有链路追踪根本没法定位。
Jaeger 是 Uber 开源的分布式追踪系统,也是 CNCF 的毕业项目。它的核心概念有三个:
- Trace:一次完整的请求链路
- Span:链路中的一个操作单元
- Context:跨服务传递的上下文信息
2.3.1 Jaeger 架构
Jaeger 的架构其实不复杂:
- 应用通过 SDK 发送 Span 数据
- Agent 负责接收并转发
- Collector 做数据聚合和存储
- Query 服务提供查询 API
- UI 界面展示链路图
下面我用一张 SVG 图来展示整个监控数据采集的知识体系:
2.3.2 集成 Jaeger
在 Go 服务中集成 Jaeger 非常简单。下面是一个最小示例:
import (
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
func initJaeger() (opentracing.Tracer, io.Closer) {
cfg := &config.Configuration{
ServiceName: "order-service",
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1, // 全量采样,生产环境建议用概率采样
},
Reporter: &config.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "jaeger-agent:6831",
},
}
tracer, closer, _ := cfg.NewTracer()
return tracer, closer
}
注意:全量采样(Param=1)在低流量环境没问题。但如果是高并发系统,建议用概率采样(比如 0.1),否则存储压力会很大。我曾经见过一个日活千万的系统,全量采样一天产生 2TB 的链路数据,Elasticsearch 直接被打挂了。
2.4 三种数据的协同
日志、指标、链路追踪不是孤立的。在实际监控中,它们需要协同工作。
举个例子:
- Prometheus 告警说「订单接口延迟超过 5 秒」
- 去 Jaeger 看链路,发现「支付服务」的 Span 耗时最长
- 去 Elasticsearch 搜支付服务的日志,发现「数据库连接池耗尽」
你看,三个数据源串起来,问题定位就快了。我个人习惯在告警通知里直接附上 Jaeger 的 Trace ID 和日志的查询链接,这样值班人员点一下就能看到上下文。
我的建议:在业务代码中,把 Trace ID 打印到日志里。这样日志和链路就能关联起来。具体做法是在日志框架的 MDC(Mapped Diagnostic Context)中注入 Trace ID。
好了,监控数据采集这块就聊到这。记住一个原则:日志要全,指标要准,链路要细。三者配合,才能构建一个立体的监控体系。
公众号:蓝海资料掘金营,微信deep3321