3、日志采集架构:Agent采集模式、无侵入式采集、日志传输管道设计
日志采集,是整个可观测性体系的「水源」。水源不干净,后面分析再牛也是白搭。我见过太多团队,花大价钱搭了ELK,结果采集端没搞好,日志丢了一半,或者把业务进程拖垮了。今天咱们就把采集架构这块掰开揉碎,讲清楚。
3.1 Agent采集模式:最主流,也最考验功底
Agent模式,说白了就是在每台机器上装一个采集进程。它负责读日志文件,然后发到后端。这是目前最成熟、最灵活的方式。
核心组件拆解
- 文件监听器:监控日志文件变化,支持轮转(rotation)
- 读取器:按行读取,支持断点续传(记录偏移量)
- 缓冲区:内存或磁盘缓冲,防止突发流量打崩后端
- 发送器:将日志批量压缩后发送
我个人习惯用 Filebeat 或 Fluent Bit。它们轻量,内存占用控制在 10-30MB 左右。你想想看,如果每个 Agent 吃掉 200MB 内存,1000 台机器就是 200GB,这成本谁扛得住?
关键指标:Agent 自身 CPU 占用不应超过单核的 5%,内存不超过 50MB。超过这个阈值,就得优化了。
断点续传怎么实现?
嗯,这里要注意。Agent 必须记录每个文件的 inode 和读取偏移量。我遇到过一个问题:日志轮转时,旧文件被重命名,新文件创建。如果 Agent 只认文件名,就会漏掉轮转间隙的日志。解决办法是:同时监控 inode 和文件名,轮转后立即打开新文件,并记录旧文件的最后位置。
# 伪代码示例:断点续传逻辑
def tail_file(path):
inode = get_inode(path)
offset = load_offset(inode) # 从本地状态文件读取
with open(path, 'r') as f:
f.seek(offset)
for line in f:
send(line)
save_offset(inode, f.tell()) # 实时保存
避坑指南:我曾经在生产环境遇到过 Agent 把磁盘写满的惨案。原因是缓冲区设置过大,又没有限制磁盘使用量。建议给 Agent 的磁盘缓冲设一个硬上限,比如 1GB,超过就丢弃旧数据或阻塞读取。
3.2 无侵入式采集:不动业务代码,也能拿到日志
有些场景,你没法装 Agent。比如容器环境、Serverless、或者第三方闭源软件。这时候就需要无侵入式采集。
常见手段:
- 标准输出(stdout)劫持:容器环境下,应用把日志打到 stdout,由容器运行时(如 Docker)收集。这是 K8s 的默认做法。
- 系统调用劫持:通过 LD_PRELOAD 或 eBPF 拦截 write() 系统调用,直接捕获日志内容。适合老旧系统。
- 日志文件挂载:把宿主机的日志目录挂载到采集容器里,由采集容器读取。注意权限问题。
我个人比较推荐 eBPF 方案。它不需要改应用代码,也不需要重启进程。但 eBPF 也有坑——内核版本要求高,而且调试起来很痛苦。我记得有一次,eBPF 程序在内核 4.19 上跑得好好的,升级到 5.10 就挂了,查了两天才发现是 helper 函数接口变了。
警告:无侵入式采集虽然方便,但无法获取应用内部的上下文信息(比如用户 ID、请求追踪 ID)。如果你需要做全链路追踪,还是得靠 Agent 或 SDK 打点。
3.3 日志传输管道设计:别让数据堵在路上
采集完了,怎么把日志从几千台机器送到中心存储?这就是传输管道要解决的问题。管道设计不好,轻则丢日志,重则把网络打满。
管道架构分层:
| 层级 | 组件 | 职责 |
|---|---|---|
| 采集层 | Agent(Filebeat/Fluent Bit) | 本地采集、简单过滤、压缩 |
| 缓冲层 | Kafka / Redis | 削峰填谷,解耦生产与消费 |
| 消费层 | Logstash / Fluentd | 解析、转换、路由 |
| 存储层 | Elasticsearch / ClickHouse | 索引、存储、查询 |
为什么要加 Kafka?
你想想看,如果 Agent 直接往 Elasticsearch 写数据,ES 一旦慢(比如合并段、GC停顿),Agent 就会积压,然后 OOM。加一层 Kafka 之后,Agent 只管往 Kafka 写,Kafka 的吞吐量极高(单机几十万条/秒),ES 慢慢消费就行。说白了,Kafka 就是给管道加了一个「蓄水池」。
压缩与协议选择:
- 传输前必须压缩。gzip 压缩比高,但耗 CPU;snappy/lz4 速度快,压缩比稍低。我建议用 lz4,平衡性好。
- 协议上,优先用 gRPC 或 HTTP/2。它们支持流式传输,比 HTTP/1.1 的短连接效率高很多。
# Fluent Bit 配置示例:输出到 Kafka
[OUTPUT]
Name kafka
Brokers 192.168.1.10:9092,192.168.1.11:9092
Topics app-logs
Format json
rdkafka.compression.codec lz4
rdkafka.queue.buffering.max.messages 100000
避坑指南:我曾经遇到过一个诡异问题:Kafka 消费端偶尔会重复消费同一条日志。查了半天,发现是 Agent 发送后没等 Kafka 确认就认为发送成功,然后 Kafka 分区 leader 切换导致数据重平衡。解决办法:设置 acks=all,确保数据写入所有副本后才算成功。
背压与降级策略:
管道必须能处理下游故障。我常用的策略是:
- 本地磁盘缓冲:当 Kafka 不可用时,Agent 把日志暂存到本地磁盘。
- 指数退避重试:第一次重试等 1 秒,第二次 2 秒,第三次 4 秒……最大 60 秒。
- 最终丢弃:如果缓冲超过 1 小时,丢弃最旧的日志,保证 Agent 不崩溃。
嗯,这里要注意:降级策略一定要有监控告警。如果 Agent 开始丢日志了,你得第一时间知道,而不是等到排查问题时才发现数据不全。
总结一下:日志采集架构没有银弹。Agent 模式适合你能控制的机器,无侵入式适合容器和遗留系统,传输管道则要重点考虑缓冲和背压。把这三点搞扎实了,你的日志系统就成功了一半。