第3章:日志传输与缓冲:Kafka在日志链路中的角色
聊到日志系统,很多人第一反应就是ELK。但说实话,如果没有Kafka在中间撑着,你那套ELK大概率撑不过三个月。我见过太多团队,日志一多直接把Elasticsearch写挂了,然后整个集群雪崩。
Kafka在日志链路里扮演什么角色?说白了就三个字:缓冲层。它像一个大水池,上游的日志生产者只管往里倒水,下游的消费者按自己的节奏慢慢喝。这样谁都不会被冲垮。
3.1 Kafka在日志链路中的核心定位
先看一个典型的日志链路:
应用服务 → Filebeat/Logstash → Kafka → Logstash → Elasticsearch → Kibana
Kafka夹在中间,干了几件大事:
- 削峰填谷:业务高峰期日志量暴增,Kafka帮你扛住,下游慢慢消费
- 解耦:日志生产和消费不再强依赖,生产者挂了不影响消费者
- 多路复用:一份日志可以同时被多个消费者消费,比如实时告警和离线分析
- 持久化:日志不会丢,哪怕下游全挂了,Kafka里还存着
核心观点:没有Kafka的日志系统,就像没有缓冲区的管道,压力一大就崩。
我记得有一次帮客户排查问题,他们的日志系统一到双十一就挂。我一看架构,Filebeat直连Elasticsearch。嗯,这不挂才怪。后来加了Kafka,稳如老狗。
3.2 Topic设计原则
Topic是Kafka里最基础的概念。设计得好不好,直接影响整个系统的性能和可维护性。
3.2.1 按业务域划分
我个人习惯,Topic的命名规则是:{业务线}.{服务名}.{日志类型}。比如:
order.payment.access
order.payment.error
user.login.behavior
user.register.metric
为什么要这么分?你想想看,如果所有日志都塞到一个Topic里,下游消费的时候还得做过滤,性能差不说,维护起来也头疼。
3.2.2 控制Topic数量
Topic不是越多越好。我见过有人一个服务建了50个Topic,结果运维起来想死。一般来说:
- 一个服务3-5个Topic就够了
- 按日志级别分:info、warn、error
- 按业务类型分:access、behavior、metric
小技巧:如果日志量特别大,可以按时间维度分Topic,比如每天一个。但别分太细,否则消费者管理起来很麻烦。
3.2.3 合理设置分区数
分区数决定了Kafka的并行处理能力。但也不是越大越好。
| 分区数 | 优点 | 缺点 |
|---|---|---|
| 太少(1-3) | 管理简单 | 吞吐量低,消费者并行度不够 |
| 适中(6-12) | 吞吐量和性能平衡 | 需要合理规划消费者数量 |
| 太多(>50) | 理论上吞吐量高 | 文件句柄多,Leader选举慢,运维复杂 |
我建议:分区数 = 消费者线程数 × 2。这样既能充分利用消费者,又留有余量。
3.3 分区与副本策略
分区和副本,是Kafka高可用和高性能的基石。这块坑不少,我踩过。
3.3.1 分区策略
消息怎么分配到分区?默认是轮询。但日志场景下,我建议用key-based策略。
// 按服务实例ID分区,保证同一实例的日志有序
producer.send(new ProducerRecord<>("order.log", instanceId, logMessage));
为什么要这样做?因为日志分析经常需要按时间线看一个实例的行为。如果分散到不同分区,排序就麻烦了。
3.3.2 副本策略
副本数一般设2或3。多了浪费存储,少了不安全。
- 副本数=2:能容忍1个Broker挂掉
- 副本数=3:能容忍2个Broker挂掉,但存储成本高50%
注意:副本数不能超过Broker数量。比如你有3台Broker,副本数最多设3。我曾经见过有人设了5个副本,结果只有3台机器,副本永远同步不完。
3.3.3 ISR机制
ISR(In-Sync Replicas)是Kafka保证数据一致性的关键。只有ISR里的副本才能参与Leader选举。
配置参数:
# 最小ISR数量,建议设为副本数-1
min.insync.replicas=2
# 生产者的ACK策略,建议设为all
acks=all
我曾经踩过一个坑:副本数设了3,但min.insync.replicas设了1。结果一个Broker挂了,数据写入成功,但实际只有1个副本有数据。后来另一个Broker也挂了,数据直接丢了。嗯,从那以后我再也不敢偷懒了。
3.4 数据可靠性保证
日志丢了,问题排查就无从谈起。所以数据可靠性是Kafka配置的重中之重。
3.4.1 生产者端
生产者配置直接影响数据是否丢失:
# 关键配置
acks=all # 等待所有副本确认
retries=3 # 重试次数
max.in.flight.requests.per.connection=1 # 保证顺序
enable.idempotence=true # 开启幂等性,防止重复
为什么acks要设all?因为默认的acks=1只保证Leader写成功,如果Leader挂了,数据就丢了。设成all,所有ISR副本都写成功才返回,虽然慢一点,但安全。
3.4.2 Broker端
Broker端的配置同样重要:
# 关键配置
default.replication.factor=3 # 默认副本数
min.insync.replicas=2 # 最小ISR数量
unclean.leader.election.enable=false # 不允许非ISR副本成为Leader
重点:unclean.leader.election.enable一定要设为false。否则当所有ISR副本都挂了,Kafka会选一个数据不全的副本当Leader,导致数据丢失。宁可不可用,也不能丢数据。
3.4.3 消费者端
消费者端也要注意:
# 关键配置
enable.auto.commit=false # 手动提交偏移量
auto.offset.reset=earliest # 从最早开始消费
我建议手动提交偏移量。自动提交虽然方便,但万一处理完还没提交就挂了,重启后会重复消费。手动提交可以精确控制:
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// 处理日志
processLog(record);
}
// 处理完再提交
consumer.commitSync();
}
3.5 实战避坑指南
最后分享几个我踩过的坑:
- 分区数不能减少:Kafka不支持减少分区数,设计时一定要留余量
- 消息体不要太大:单条消息超过1MB会影响性能,日志一般控制在100KB以内
- 监控磁盘使用率:Kafka日志默认保留7天,磁盘满了会直接挂掉
- 合理设置保留策略:按时间和大小双重限制,比如保留7天或100GB
我的习惯:每个Topic都设置retention.ms=604800000(7天)和retention.bytes=107374182400(100GB),哪个先到就删。这样既保证有足够时间处理,又不会撑爆磁盘。
好了,Kafka在日志链路中的角色就聊到这。下一章我们讲日志采集层的实战,到时候会手把手教你配Filebeat和Logstash。