日志采集基础:采集原理、Agent部署、数据流
日志采集,说白了就是三个步骤:发现 -> 读取 -> 发送。机器上先有日志文件,采集器才知道去读它。采集器读到新内容,再打包发给后面的管道。嗯,就这么简单。但为什么实际排查时总有人搞丢数据?
我个人习惯,在讲复杂知识点前,先把最基础的东西讲透。因为丢失排查的根因,十有八九是埋在了这些“你觉得自己很清楚”的原理里。
采集原理:三大模式
我知道市面上有各种采集器,Filebeat、Fluentd、Logstash、咱们自研的Agent等等。但它们的底层逻辑,归纳起来就三种。
| 模式 | 核心原理 | 典型工具 | 我最怕的情况 |
|---|---|---|---|
| 文件 tail 模式 | 像 tail -f 一样,记录 inode 和偏移量 | Filebeat、Fluentd tail input | 日志轮转时 offset 没跟上,丢了一个文件的内容 |
| 网络监听模式 | Agent 作为客户端,监听 TCP/UDP 端口 | syslog-ng、Logstash TCP input | 网络抖动导致数据包丢失,而且没有重传机制 |
| API 轮询模式 | 定时调用云平台或应用的 API 拉取日志 | 阿里云SLS、AWS CloudWatch | API 限流或返回空结果,但 Agent 以为“没有日志” |
最重要的一件事:无论哪种模式,采集器自身都是有状态的。它必须知道自己读到了哪一行。这个状态一旦丢失,必丢数据。
我在项目里见过有人贪方便,把所有日志都写到同一个文件里。结果日志量一大,单个Agent根本赶不上写入的速度。offset不停地被追,最后那个文件一旦被轮转,Agent还没来得及读完,老文件就被删了。嗯,数据就这么蒸发掉了。
为什么会这样?因为文件 tail 模式依赖 inode 来跟踪文件。轮转后,新文件的 inode 变了。如果 Agent 处理不当,它会认为“这是个新文件”,直接从新文件的开头读。旧文件后半段的内容,就再也找不到了。
Agent 部署模式:集中 vs 分散
我手头有好几个运维老项目,日志格式可谓是“各自为政”。有的写在 /var/log/,有的写在 /app/logs/,甚至还有的直接写数据库。所以部署模式,说白了是解决“怎么把日志接进来”的问题。
我习惯把 Agent 分成三大类:轻量级 Shipper、富特性 Collector 和 全功能 Sidecar。
- 轻量级 Shipper:只负责读和发,不做解析。像 Filebeat,内存占用小,适合每台机器都装。我建议在边缘节点用这种,坏了就重启,不心疼。
- 富特性 Collector:能解析、过滤、聚合。像 Logstash,但内存吃得多。我一般只在集中处理节点用,不在边缘装。
- 全功能 Sidecar:跟容器应用绑定,每个 Pod 一个。适合容器化改造后的场景。我曾经遇到一个坑:如果容器重启太频繁,Sidecar 的 checkpoint 没来得及写回硬盘,一重启状态就丢了。
注意:Agent 不是装得越多越好。一个节点上装两份采集不同日志的 Agent,如果它们同时读同一个文件,就会出现 双读竞争。我见过因为竞争导致日志顺序错乱,下游解析器直接报错。
部署时,你还需要考虑 协议。是用 TCP 还是 UDP?我建议:能 TCP 就别用 UDP。UDP 无状态、无重试,网络稍微拥堵,数据包就丢了。你都不知道它什么时候丢的。有一次我排查丢日志,最后发现是采集器和接收方之间的 UDP 端口被防火墙限流了。
下面是一个典型的 Agent 配置示例(我用 Python 伪代码示范):
# agent_config.py
agent = {
"mode": "tail",
"paths": ["/var/log/app/*.log"],
"encoding": "utf-8",
"offset_file": "/var/lib/agent/offset.db", # 核心:偏移量持久化
"output": {
"type": "kafka",
"bootstrap_servers": ["10.0.0.1:9092"],
"topic": "app-logs",
"compression": "gzip"
},
"retry": {
"max_retries": 5,
"backoff_sec": 3
}
}
小技巧:我一直强调 offset 要写 本地持久化存储。不要只依赖内存。否则 Agent 一重启,它就失忆了。你以为它还在追,其实它已经追丢了十几分钟的数据。
数据流:一次完整的采集旅程
把 Agent 装好,配置写好,数据怎么流动?我得画一张流程图,这样回头看排查思路才清晰。
你看,数据从应用产生,到 Agent 采集,再到输出、传输、消费,每个环节都可能出问题。我最关注的其实是 输出侧到传输链路 这一段。因为输出的数据量一旦超过链路带宽,就会触发反压或丢包。
我建议你在搭建时就给每个环节留一个 数据流快照。说白了就是在 Agent 和接收方之间,加个简单的计数心跳。Agent 每发一条日志,就记一个 seq number。接收方收到后,能发现号码是不是跳了。跳了?那中间肯定有丢的。
| 阶段 | 流转方向 | 常见问题 |
|---|---|---|
| 日志写入 | 应用 → 磁盘 | 日志缓冲区没刷盘,宕机后丢失 |
| Agent 轮询 | 磁盘 → Agent 内存 | Agent 读太快或太慢,追不上写入速度 |
| 输出 | Agent 内存 → 网络 | 输出队列积压,导致 Agent 崩溃 |
| 缓冲传输 | 网络 → Kafka/Redis | 网络分区、Kafka leader宕机、写入超时 |
| 写入目标 | Kafka/Redis → 存储 | 分区数不一致导致数据倾斜,某些分区累积 |
| 消费 | 存储 → 审计服务 | 消费者 lag 太大,数据被清理策略自动删除 |
我刚开始做监控时,也想过“文件系统还能丢?”别不信。我遇到过极端情况:磁盘 I/O 被打满,应用在写日志,但写操作被内核延迟了。Agent 读的时候看到的文件内容不完整,等 I/O 恢复,文件已经变了。这就是读到的日志不完整,或者干脆读到了半行。
总结一下这一章的核心:理解采集原理,是为了知道哪里可能丢;了解 Agent 部署,是为了避免部署层面的坑;掌握数据流,是为了在排查时能快速定位出问题的环节。 你想想看,如果连数据从哪到哪都不清楚,排查丢日志岂不是大海捞针?