本章目标
本章从底层解释 Kafka 为什么吞吐高、为什么能容错,以及什么配置会影响丢消息和重复消息。
Kafka 日志存储模型
Kafka 的 partition 本质是追加日志。每个 partition 在磁盘上对应一个目录,目录中有多个日志段文件。
典型文件:
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 删除数据不是因为消费者消费了,而是因为保留策略。
常见配置:
retention.ms=604800000 retention.bytes=107374182400 segment.bytes=1073741824 segment.ms=604800000含义:
retention.ms:保留多久。retention.bytes:最多保留多大。segment.bytes:单个日志段大小。segment.ms:日志段滚动时间。
Delete 策略
默认策略,超过时间或大小后删除旧日志段。
适合:
- 行为日志。
- 订单事件流水。
- 操作日志。
Compact 策略
按 key 保留最新值,旧值会被压缩清理。
适合:
- 用户最新状态。
- 配置变更。
- 数据库 CDC 的最新快照。
配置示例:
kafka-configs --bootstrap-server localhost:9092\--entity-type topics\--entity-name user-profile-snapshot\--alter\--add-configcleanup.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 开始:
acks=all enable.idempotence=true retries=2147483647 max.in.flight.requests.per.connection=5 delivery.timeout.ms=120000 request.timeout.ms=30000acks=0
Producer 发出去就认为成功,不等待 broker。吞吐高,但可能丢消息。
适合:可丢弃日志、埋点采样。
acks=1
Leader 写入成功就返回。leader 宕机且 follower 未同步时可能丢消息。
适合:一般日志,但不适合核心交易。
acks=all
Leader 等 ISR 中副本确认后返回。配合min.insync.replicas可以显著降低丢消息风险。
生产建议:
replication.factor=3 min.insync.replicas=2 acks=all unclean.leader.election.enable=false含义:3 副本中至少 2 个同步副本确认,才认为写入成功;不同步副本不能被选为 leader。
幂等生产者
幂等生产者解决“发送成功但响应丢失,producer 重试导致重复写入”的问题。
开启:
enable.idempotence=trueKafka 为 producer 分配 PID,并为每个 partition 维护 sequence number。broker 发现同一 PID、同一 partition 上重复 sequence,会去重。
边界:
- 幂等只保证单 producer session 内、单 partition 上的写入不重复。
- producer 重启后 PID 变化,业务层仍建议有
eventId做幂等。
Kafka 事务
Kafka 事务解决“多条消息要么一起对消费者可见,要么一起不可见”的问题。
配置:
transactional.id=order-tx-producer-1 enable.idempotence=true事务流程:
beginTransaction send topic A send topic B sendOffsetsToTransaction commitTransaction消费者如果只想读已提交事务数据:
isolation.level=read_committed适用场景:
- 从一个 topic 消费,处理后写入另一个 topic,同时提交消费 offset。
- Kafka Streams exactly-once 处理。
不适用场景:
- 直接保证数据库和 Kafka 的强一致事务。数据库不参与 Kafka 事务。
- 跨外部 HTTP 服务的全局事务。
数据库 + Kafka 更常用的是 Outbox 模式:
业务事务写订单表 + outbox_event 表 -> 后台任务/CDC 发送 Kafka -> 标记已发送Consumer 可靠性
Consumer 可靠性重点不是 Kafka 能否保存消息,而是 offset 提交时机。
错误做法:
poll -> commit offset -> 业务处理处理失败后消息不会再被消费。
推荐做法:
poll -> 业务处理成功 -> commit offset如果业务处理成功但提交 offset 失败,消息可能重复消费。因此消费者业务必须支持幂等。
端到端语义
| 语义 | 条件 | 说明 |
|---|---|---|
| At most once | 先提交 offset 后处理 | 可能丢,不重复 |
| At least once | 处理成功后提交 offset | 不易丢,可能重复 |
| Exactly once | Kafka 事务 + 幂等 + read_committed | 只覆盖 Kafka 内链路 |
在业务系统中,最常见、最务实的是:
Kafka 至少一次投递 + 消费端业务幂等实操:可靠性配置检查
查看 topic 配置:
dockercomposeexeckafka kafka-configs\--bootstrap-server localhost:9092\--entity-type topics\--entity-name order-events\--describe创建 3 副本 topic 的生产命令在单 broker demo 中不可用,但生产环境应类似:
kafka-topics --bootstrap-server kafka-1:9092\--create\--topicorder-events\--partitions12\--replication-factor3\--configmin.insync.replicas=2检查 ISR:
kafka-topics --bootstrap-server kafka-1:9092\--describe\--topicorder-events重点看:
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 聚合成批次。
关键配置:
linger.ms=10 batch.size=65536 buffer.memory=67108864 compression.type=lz4调优思路:
- 延迟敏感:
linger.ms小一些,例如 0-5ms。 - 吞吐优先:
linger.ms适当增大,例如 10-50ms。 - 消息较小:提高
batch.size更容易合批。 - 网络或磁盘压力大:开启
lz4或zstd压缩。
可靠性与吞吐取舍
| 配置 | 吞吐 | 可靠性 | 说明 |
|---|---|---|---|
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、读写延迟。
关键配置:
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 线程
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 调优
批处理
max.poll.records=500 fetch.min.bytes=1048576 fetch.max.wait.ms=500 max.partition.fetch.bytes=1048576如果消费逻辑支持批量写库,应该尽量批处理:
poll 500 条 -> 批量校验 -> 批量写入数据库 -> 提交 offset比每条消息一次数据库写入更稳定。
背压
当下游数据库或 HTTP 服务变慢时,消费者继续高速拉取会导致内存和线程池堆积。
策略:
- 降低
max.poll.records。 - 暂停对应 partition:
consumer.pause(partitions)。 - 下游恢复后再
resume。 - 对非核心业务使用限流和降级。
- 对核心业务保留堆积容量和告警阈值。
容量规划
输入指标
容量规划至少需要这些数字:
| 指标 | 示例 | 用途 |
|---|---|---|
| 峰值 TPS | 30,000 msg/s | 估算请求量 |
| 平均消息大小 | 1 KB | 估算带宽和磁盘 |
| 保留时间 | 7 天 | 估算存储 |
| 副本数 | 3 | 存储乘数 |
| 压缩比 | 0.5 | 修正存储和网络 |
| 目标峰值利用率 | 60% | 保留冗余 |
存储估算
公式:
每日原始数据量 = TPS * 消息大小 * 86400 实际存储 = 每日原始数据量 * 保留天数 * 副本数 * 压缩比 / 磁盘目标利用率示例:
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,峰值写入约:
30000 msg/s * 1KB = 30 MB/s写入角度至少 3 个 partition。但消费角度如果需要 24 个消费者并行处理,则 topic 至少要 24 个 partition。
建议:
partition = max(写入吞吐所需, 消费并行度所需) * 未来增长系数压测工具
Kafka 自带压测脚本:
Producer 压测:
kafka-producer-perf-test\--topicperf-test\--num-records1000000\--record-size1024\--throughput-1\--producer-propsbootstrap.servers=localhost:9092acks=allcompression.type=lz4Consumer 压测:
kafka-consumer-perf-test\--bootstrap-server localhost:9092\--topicperf-test\--messages1000000\--groupperf-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。