Kafka :存储、复制与可靠性

本章目标

本章从底层解释 Kafka 为什么吞吐高、为什么能容错,以及什么配置会影响丢消息和重复消息。

Kafka 日志存储模型

Kafka 的 partition 本质是追加日志。每个 partition 在磁盘上对应一个目录,目录中有多个日志段文件。

典型文件:

text 复制代码
00000000000000000000.log
00000000000000000000.index
00000000000000000000.timeindex
00000000000001000000.log
00000000000001000000.index
00000000000001000000.timeindex
文件 作用
.log 保存真实消息内容
.index offset 到物理位置的稀疏索引
.timeindex 时间戳到 offset 的索引

Kafka 高吞吐的核心原因:

  • 顺序追加写磁盘,避免随机写。
  • 充分利用 OS page cache。
  • 批量发送和批量落盘。
  • sendfile / zero-copy 降低内核态和用户态拷贝。
  • partition 并行分布在多个 broker。

Retention 与 Compaction

Kafka 删除数据不是因为消费者消费了,而是因为保留策略。

常见配置:

properties 复制代码
retention.ms=604800000
retention.bytes=107374182400
segment.bytes=1073741824
segment.ms=604800000

含义:

  • retention.ms:保留多久。
  • retention.bytes:最多保留多大。
  • segment.bytes:单个日志段大小。
  • segment.ms:日志段滚动时间。

Delete 策略

默认策略,超过时间或大小后删除旧日志段。

适合:

  • 行为日志。
  • 订单事件流水。
  • 操作日志。

Compact 策略

按 key 保留最新值,旧值会被压缩清理。

适合:

  • 用户最新状态。
  • 配置变更。
  • 数据库 CDC 的最新快照。

配置示例:

bash 复制代码
kafka-configs --bootstrap-server localhost:9092 \
  --entity-type topics \
  --entity-name user-profile-snapshot \
  --alter \
  --add-config cleanup.policy=compact

副本复制

每个 partition 可以有多个 replica。一个 replica 是 leader,其余是 follower。

Producer 和 Consumer 默认只与 leader 交互,follower 从 leader 拉取数据。

关键概念:

概念 说明
Leader 当前处理读写请求的副本
Follower 从 leader 复制数据的副本
AR Assigned Replicas,所有分配副本
ISR In-Sync Replicas,与 leader 保持同步的副本
OSR Out-of-Sync Replicas,落后太多的副本
LEO Log End Offset,日志末尾位置
HW High Watermark,消费者可见的最高已提交位置

ISR 与 HW

Kafka 不会把 leader 刚写入但尚未被足够副本确认的消息立即暴露为"稳定数据"。HW 表示已经被 ISR 副本确认的安全位置,消费者只能读到 HW 之前的消息。

这解决的问题:

  • leader 写入一条消息后立刻宕机。
  • follower 没来得及复制。
  • 新 leader 不包含那条消息。
  • 如果消费者之前已经读到那条消息,就会出现"读到的数据后来消失"。

Kafka 通过 HW 避免消费者读到未提交数据。

Producer 可靠性配置

可靠性从 producer 开始:

properties 复制代码
acks=all
enable.idempotence=true
retries=2147483647
max.in.flight.requests.per.connection=5
delivery.timeout.ms=120000
request.timeout.ms=30000

acks=0

Producer 发出去就认为成功,不等待 broker。吞吐高,但可能丢消息。

适合:可丢弃日志、埋点采样。

acks=1

Leader 写入成功就返回。leader 宕机且 follower 未同步时可能丢消息。

适合:一般日志,但不适合核心交易。

acks=all

Leader 等 ISR 中副本确认后返回。配合 min.insync.replicas 可以显著降低丢消息风险。

生产建议:

properties 复制代码
replication.factor=3
min.insync.replicas=2
acks=all
unclean.leader.election.enable=false

含义:3 副本中至少 2 个同步副本确认,才认为写入成功;不同步副本不能被选为 leader。

幂等生产者

幂等生产者解决"发送成功但响应丢失,producer 重试导致重复写入"的问题。

开启:

properties 复制代码
enable.idempotence=true

Kafka 为 producer 分配 PID,并为每个 partition 维护 sequence number。broker 发现同一 PID、同一 partition 上重复 sequence,会去重。

边界:

  • 幂等只保证单 producer session 内、单 partition 上的写入不重复。
  • producer 重启后 PID 变化,业务层仍建议有 eventId 做幂等。

Kafka 事务

Kafka 事务解决"多条消息要么一起对消费者可见,要么一起不可见"的问题。

配置:

properties 复制代码
transactional.id=order-tx-producer-1
enable.idempotence=true

事务流程:

text 复制代码
beginTransaction
send topic A
send topic B
sendOffsetsToTransaction
commitTransaction

消费者如果只想读已提交事务数据:

properties 复制代码
isolation.level=read_committed

适用场景:

  • 从一个 topic 消费,处理后写入另一个 topic,同时提交消费 offset。
  • Kafka Streams exactly-once 处理。

不适用场景:

  • 直接保证数据库和 Kafka 的强一致事务。数据库不参与 Kafka 事务。
  • 跨外部 HTTP 服务的全局事务。

数据库 + Kafka 更常用的是 Outbox 模式:

text 复制代码
业务事务写订单表 + outbox_event 表 -> 后台任务/CDC 发送 Kafka -> 标记已发送

Consumer 可靠性

Consumer 可靠性重点不是 Kafka 能否保存消息,而是 offset 提交时机。

错误做法:

text 复制代码
poll -> commit offset -> 业务处理

处理失败后消息不会再被消费。

推荐做法:

text 复制代码
poll -> 业务处理成功 -> commit offset

如果业务处理成功但提交 offset 失败,消息可能重复消费。因此消费者业务必须支持幂等。

端到端语义

语义 条件 说明
At most once 先提交 offset 后处理 可能丢,不重复
At least once 处理成功后提交 offset 不易丢,可能重复
Exactly once Kafka 事务 + 幂等 + read_committed 只覆盖 Kafka 内链路

在业务系统中,最常见、最务实的是:

text 复制代码
Kafka 至少一次投递 + 消费端业务幂等

实操:可靠性配置检查

查看 topic 配置:

bash 复制代码
docker compose exec kafka kafka-configs \
  --bootstrap-server localhost:9092 \
  --entity-type topics \
  --entity-name order-events \
  --describe

创建 3 副本 topic 的生产命令在单 broker demo 中不可用,但生产环境应类似:

bash 复制代码
kafka-topics --bootstrap-server kafka-1:9092 \
  --create \
  --topic order-events \
  --partitions 12 \
  --replication-factor 3 \
  --config min.insync.replicas=2

检查 ISR:

bash 复制代码
kafka-topics --bootstrap-server kafka-1:9092 \
  --describe \
  --topic order-events

重点看:

text 复制代码
Leader: 1 Replicas: 1,2,3 Isr: 1,2,3

如果 ISR 长期少于副本数,说明 follower 落后或 broker 异常,需要排查网络、磁盘、GC、负载。

04 性能调优、压测与容量规划

本章目标

Kafka 调优不是记一堆参数,而是围绕目标吞吐、延迟、可靠性和成本做取舍。本章给出可落地的调优路线:

  • Producer 批量、压缩、并发。
  • Broker 磁盘、网络、线程、页缓存。
  • Consumer 拉取、批处理、并发和背压。
  • Topic 分区和容量规划。
  • 压测方法与指标解释。

性能问题先分类

表现 可能原因 优先排查
Producer 发送慢 批次太小、网络慢、broker 写入慢 producer metrics、request latency
Consumer 堆积 消费逻辑慢、分区太少、下游慢 lag、处理耗时、线程池
Broker CPU 高 压缩消耗、请求太多、TLS/SASL CPU、请求队列、网络线程
Broker 磁盘忙 顺序写压力大、page cache 不足 iostat、log flush、磁盘延迟
Rebalance 频繁 消费者心跳超时、实例波动 consumer group logs
某分区热点 key 分布不均 partition bytes in/out

Producer 调优

批量发送

Producer 不是每条消息都立刻发送一个网络请求,而是先进入本地缓冲区,按 topic-partition 聚合成批次。

关键配置:

properties 复制代码
linger.ms=10
batch.size=65536
buffer.memory=67108864
compression.type=lz4

调优思路:

  • 延迟敏感:linger.ms 小一些,例如 0-5ms。
  • 吞吐优先:linger.ms 适当增大,例如 10-50ms。
  • 消息较小:提高 batch.size 更容易合批。
  • 网络或磁盘压力大:开启 lz4zstd 压缩。

可靠性与吞吐取舍

配置 吞吐 可靠性 说明
acks=0 不等确认
acks=1 中高 leader 写入即成功
acks=all 等 ISR 确认
compression.type=none CPU 低 不直接影响 网络和磁盘压力高
compression.type=lz4 常见较优 不直接影响 综合性能好

Broker 调优

磁盘

Kafka 强依赖磁盘顺序 IO。生产建议:

  • 使用 SSD 或高性能云盘。
  • 日志目录分散到多块盘。
  • 保留足够 page cache。
  • 不要把 broker 和重 IO 服务混部。
  • 监控磁盘使用率、IO wait、读写延迟。

关键配置:

properties 复制代码
log.dirs=/data/kafka-logs-1,/data/kafka-logs-2
log.segment.bytes=1073741824
log.retention.hours=168
num.recovery.threads.per.data.dir=2

网络线程和 IO 线程

properties 复制代码
num.network.threads=8
num.io.threads=16
queued.max.requests=500
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400

调优原则:

  • 请求队列积压说明 broker 处理不过来。
  • 网络线程不足时 request queue 会升高。
  • IO 线程不足时磁盘相关处理延迟升高。
  • 不要盲目调大线程,先看 CPU 是否还有余量。

Consumer 调优

批处理

properties 复制代码
max.poll.records=500
fetch.min.bytes=1048576
fetch.max.wait.ms=500
max.partition.fetch.bytes=1048576

如果消费逻辑支持批量写库,应该尽量批处理:

text 复制代码
poll 500 条 -> 批量校验 -> 批量写入数据库 -> 提交 offset

比每条消息一次数据库写入更稳定。

背压

当下游数据库或 HTTP 服务变慢时,消费者继续高速拉取会导致内存和线程池堆积。

策略:

  • 降低 max.poll.records
  • 暂停对应 partition:consumer.pause(partitions)
  • 下游恢复后再 resume
  • 对非核心业务使用限流和降级。
  • 对核心业务保留堆积容量和告警阈值。

容量规划

输入指标

容量规划至少需要这些数字:

指标 示例 用途
峰值 TPS 30,000 msg/s 估算请求量
平均消息大小 1 KB 估算带宽和磁盘
保留时间 7 天 估算存储
副本数 3 存储乘数
压缩比 0.5 修正存储和网络
目标峰值利用率 60% 保留冗余

存储估算

公式:

text 复制代码
每日原始数据量 = TPS * 消息大小 * 86400
实际存储 = 每日原始数据量 * 保留天数 * 副本数 * 压缩比 / 磁盘目标利用率

示例:

text 复制代码
TPS = 30000
消息大小 = 1KB
保留 = 7天
副本 = 3
压缩比 = 0.5
磁盘目标利用率 = 0.7

每日原始数据 = 30000 * 1KB * 86400 = 2471 GB
实际存储 = 2471 * 7 * 3 * 0.5 / 0.7 = 37065 GB

大约需要 36 TB 可用磁盘容量。

Partition 估算

如果单 partition 写入能力按 10 MB/s,峰值写入约:

text 复制代码
30000 msg/s * 1KB = 30 MB/s

写入角度至少 3 个 partition。但消费角度如果需要 24 个消费者并行处理,则 topic 至少要 24 个 partition。

建议:

text 复制代码
partition = max(写入吞吐所需, 消费并行度所需) * 未来增长系数

压测工具

Kafka 自带压测脚本:

Producer 压测:

bash 复制代码
kafka-producer-perf-test \
  --topic perf-test \
  --num-records 1000000 \
  --record-size 1024 \
  --throughput -1 \
  --producer-props bootstrap.servers=localhost:9092 acks=all compression.type=lz4

Consumer 压测:

bash 复制代码
kafka-consumer-perf-test \
  --bootstrap-server localhost:9092 \
  --topic perf-test \
  --messages 1000000 \
  --group perf-test-group

看结果时重点关注:

  • records/sec
  • MB/sec
  • avg latency
  • p95/p99 latency
  • producer error rate
  • consumer lag

热点分区治理

热点分区常见原因:

  • key 分布不均,例如大量消息 key 都是 system
  • 某个大客户、热门商品、热门直播间流量过高。
  • 分区数量不足。

治理方法:

方法 示例 代价
key 打散 userId + randomBucket 牺牲严格顺序
大客户单独 topic vip-order-events topic 增多
增加 partition 12 -> 24 key 映射变化
分业务拆 topic 订单、支付、履约分离 架构调整

如果必须保证单订单顺序,可以按 orderId 分区;如果只需要用户级聚合,可以按 userId 分区;如果更关注吞吐,可以使用更细粒度散列 key。

相关推荐
渣渣盟1 小时前
构建企业级实时数据管道:Kafka + Flink 最佳实践
分布式·flink·kafka
KmSH8umpK3 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第四篇
数据库·redis·分布式
KmSH8umpK3 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第五篇
数据库·redis·分布式
卧室小白4 小时前
ceph-分布式存储
分布式
aXin_ya4 小时前
微服务第九天 分布式缓存(Redis)
分布式·缓存·微服务
空中海4 小时前
Spring Boot Kafka 项目 Demo:订单事件系统 专家知识、源码阅读路线与面试题
spring boot·kafka·linq
空中海5 小时前
Kafka 基础:从消息队列到事件流平台
分布式·kafka·linq
空中海7 小时前
Kafka Streams、Connect 与生态
分布式·kafka·linq
KmSH8umpK21 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第三篇
redis·分布式·wpf