3、数据采集层架构:日志采集与消息队列的高可用设计
数据采集层,说白了就是实时数据产品的「入口」。数据进不来,后面再牛的计算引擎、再炫的仪表盘都是白搭。我这些年踩过的坑,有一半都出在这一层——不是采集器挂了没人知道,就是 Kafka 集群脑裂导致数据丢失。
今天咱们就掰开揉碎,聊聊日志采集器(Filebeat / Flume)和消息队列(Kafka)的架构设计,以及怎么保证它们不掉链子。
3.1 日志采集器选型:Filebeat vs Flume
先问个问题:你手上有几百台服务器,日志散落在各个磁盘上,怎么把它们统一收上来?
市面上主流的选择就两个:Filebeat 和 Flume。我个人的习惯是——轻量场景用 Filebeat,复杂管道用 Flume。
| 对比维度 | Filebeat | Flume |
|---|---|---|
| 资源占用 | 极低(Go 编写,约 10-30MB 内存) | 较高(Java 堆,至少 1GB) |
| 部署方式 | 二进制单文件,无依赖 | 需要 JDK,配置较复杂 |
| 数据可靠性 | 内置背压 + 重试,但无事务 | 支持事务 Channel,Exactly-Once 语义 |
| 适用场景 | 日志采集 → Kafka / ES | 复杂 ETL、多路分发、跨机房传输 |
3.2 Filebeat 架构与高可用设计
Filebeat 的架构其实很简单,就三个核心组件:Harvester、Spooler、Output。
# filebeat.yml 核心配置示例
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/app/*.log
fields:
app_name: my-service
env: production
output.kafka:
hosts: ["kafka1:9092", "kafka2:9092", "kafka3:9092"]
topic: "app-logs"
partition.round_robin:
reachable_only: true
required_acks: 1
compression: gzip
max_message_bytes: 1000000
这里有个关键点——required_acks: 1。我见过不少团队为了追求吞吐量设成 0,结果 Kafka 节点一重启,数据就丢了。嗯,这里要注意:生产环境至少设成 1,确保 leader 确认收到再返回。
Filebeat 高可用三板斧
- 多 Output 负载均衡:配置多个 Kafka 节点,Filebeat 会自动做轮询。某个节点挂了,它只跳过那个节点。
- Registry 文件持久化:Filebeat 会记录每个文件读取到的偏移量。进程重启后,从断点续传,不会重复也不会漏。
- 背压机制:当 Kafka 写入变慢时,Filebeat 会自动降低读取速度,防止内存爆掉。
3.3 Flume 架构与高可用设计
Flume 的架构比 Filebeat 重,但灵活性也更高。它的核心是 Source → Channel → Sink 三段式管道。
# Flume Agent 配置示例
agent.sources = tail-source
agent.channels = file-channel
agent.sinks = kafka-sink
# Source:实时 tail 日志文件
agent.sources.tail-source.type = spooldir
agent.sources.tail-source.spoolDir = /var/log/app
agent.sources.tail-source.channels = file-channel
# Channel:使用 File Channel 保证数据不丢
agent.channels.file-channel.type = file
agent.channels.file-channel.dataDirs = /data/flume/channel
agent.channels.file-channel.checkpointDir = /data/flume/checkpoint
agent.channels.file-channel.capacity = 1000000
agent.channels.file-channel.transactionCapacity = 10000
# Sink:写入 Kafka
agent.sinks.kafka-sink.type = org.apache.flume.sink.kafka.KafkaSink
agent.sinks.kafka-sink.kafka.bootstrap.servers = kafka1:9092,kafka2:9092
agent.sinks.kafka-sink.kafka.topic = app-logs
agent.sinks.kafka-sink.channel = file-channel
Flume 的高可用核心在于 Channel 的选择。我个人强烈推荐 File Channel,而不是 Memory Channel。为什么?
- Memory Channel:快,但进程一挂数据全丢。
- File Channel:写入磁盘,Flume 重启后自动恢复,数据不丢。
- Kafka Channel:直接把数据写进 Kafka,省掉 Sink 环节,但依赖 Kafka 的可用性。
3.4 Kafka 消息队列架构设计
Kafka 是整个采集层的心脏。数据从采集器进来,经过 Kafka 缓冲,再被下游消费。它的架构设计直接决定了整个数据产品的吞吐量和可用性。
3.5 Kafka 高可用保障策略
Kafka 的高可用不是白来的,需要从几个维度去配置。我总结了一套「保命配置」,分享给你。
3.5.1 副本机制与 ISR
Kafka 的每个 Partition 可以有多个副本,其中一个是 Leader,其余是 Follower。只有 ISR(In-Sync Replica) 列表里的副本才参与 Leader 选举。
# Broker 端核心高可用配置
# 副本因子,生产环境至少 3
default.replication.factor = 3
# 最小 ISR 数量,保证写入的可靠性
min.insync.replicas = 2
# 禁用 unclean leader 选举,防止数据丢失
unclean.leader.election.enable = false
min.insync.replicas 设成了 3,结果一个 Broker 宕机后,所有写入都失败了——因为 ISR 只剩 2 个,不满足条件。后来我改成 2,配合 replication.factor=3,既保证了可靠性,又容忍单节点故障。
3.5.2 生产者确认机制
生产者的 acks 参数决定了数据写入的可靠性级别。我见过不少团队为了性能设成 0,结果一宕机数据全丢。
| acks 值 | 行为 | 可靠性 | 吞吐量 |
|---|---|---|---|
| 0 | 不等待确认,直接发下一条 | 最低(可能丢数据) | 最高 |
| 1 | 等待 Leader 确认 | 中等(Leader 宕机可能丢) | 较高 |
| all(-1) | 等待所有 ISR 确认 | 最高(不丢数据) | 较低 |
acks=1 就够了,配合 min.insync.replicas=2,性能和可靠性都兼顾。金融交易类场景必须用 acks=all。
3.5.3 消费者偏移量管理
消费者消费完数据后,需要提交偏移量(Offset)。如果提交太早,消费者挂了重启后会漏数据;提交太晚,可能重复消费。
# 消费者配置示例
props.put("enable.auto.commit", "false"); // 关闭自动提交
props.put("auto.offset.reset", "earliest"); // 从最早开始消费
// 手动提交偏移量
while (true) {
ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord record : records) {
process(record); // 先处理业务逻辑
}
consumer.commitSync(); // 处理完再提交
}
你想想看,如果先提交再处理,处理到一半进程挂了,数据就丢了。反过来,先处理再提交,最多重复消费一次。我个人习惯用 手动提交 + 处理完再提交 的模式,虽然可能重复,但绝不丢失。
3.6 端到端的高可用架构总结
把采集器和 Kafka 串起来,一个完整的高可用采集层长这样:
- 采集端:Filebeat 或 Flume 部署在每台服务器上,配置多个 Kafka 节点做负载均衡。
- 缓冲层:Kafka 集群至少 3 个 Broker,每个 Topic 的 replication.factor=3,min.insync.replicas=2。
- 消费端:Consumer Group 内多个消费者,手动提交偏移量,处理完再提交。
- 监控告警:监控采集器的运行状态、Kafka 的磁盘使用率、ISR 数量、消费延迟。