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 的典型处理流程:

  1. 输入:从 Filebeat 接收日志
  2. 过滤:用 grok 解析日志结构,提取字段
  3. 输出:写入 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 图来展示整个监控数据采集的知识体系:

监控数据采集架构总览 数据源层 应用日志 | 系统指标 | 业务指标 | 请求链路 采集层 Filebeat(日志) Prometheus Exporter(指标) Jaeger Agent(链路) 处理层 Logstash(解析过滤) Prometheus Server(拉取存储) Jaeger Collector(聚合) Elasticsearch Prometheus TSDB Elasticsearch/Cassandra

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