外观
Topic 与分区:数量、副本、保留策略
分区数一旦定错,只能加不能减。加分区又会打乱按 key 的顺序。所以这是 Kafka 里最该一次想清楚的事。
场景
建 topic 前必须回答四个问题:
- 目标写入吞吐多少?决定分区数下限。
- 未来最多几个消费者并行?决定分区数上限。
- 丢一条消息能不能接受?决定副本数与
acks。 - 数据保留多久?决定磁盘容量。
一、分区数怎么定
单分区写入吞吐的经验值:5~10 MB/s(受限于顺序写磁盘和副本复制)。
text
分区数 ≥ 峰值写入 MB/s ÷ 10
分区数 ≥ 消费线程数上限
分区数 ≤ broker 数 × 200(单 broker 2000~4000 分区是常见天花板)举例:峰值 200 MB/s、3 台 broker:
- 吞吐要求:200 / 10 = 20 分区。
- 取 24(能被 3 整除,分配均匀)。
- 单 broker 承担 8 个 leader 分区,远低于上限。
分区不是越多越好
每个分区都对应磁盘上的一组文件、一堆内存结构、一个复制线程任务。分区数上去后:
- controller 故障转移变慢(要重建所有分区的 leader)。
- 生产者端元数据变大,内存占用上升。
- 端到端延迟变高(批量变小)。
- 磁盘 inode 和文件句柄压力。
二、创建 topic
bash
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 \
--create --topic order \
--partitions 6 --replication-factor 3 \
--config min.insync.replicas=2 \
--config retention.ms=604800000 \
--config compression.type=producertext
Created topic order.| 参数 | 说明 |
|---|---|
--partitions 6 | 6 个分区,消费者组最多 6 个实例并行(多出来的会空闲) |
--replication-factor 3 | 3 副本,能容忍 1 台 broker 挂 |
min.insync.replicas=2 | 配合 acks=all,至少 2 份确认才算写成功 |
retention.ms=604800000 | 保留 7 天 |
compression.type=producer | 保留生产者发来的压缩格式,broker 不再压缩 |
查看:
bash
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --describe --topic ordertext
Topic: order TopicId: 7cQ3nR8vW1xY4zA6bC9dEf PartitionCount: 6 ReplicationFactor: 3 Configs: min.insync.replicas=2,retention.ms=604800000,compression.type=producer
Topic: order Partition: 0 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
Topic: order Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
Topic: order Partition: 2 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
Topic: order Partition: 3 Leader: 1 Replicas: 1,3,2 Isr: 1,3,2
Topic: order Partition: 4 Leader: 2 Replicas: 2,1,3 Isr: 2,1,3
Topic: order Partition: 5 Leader: 3 Replicas: 3,2,1 Isr: 3,2,1读法:
- 每行一个分区。
Replicas第一个是 preferred leader。 Isr长度等于副本数才算健康。- leader 分布在 1/2/3 上,均匀。
三、副本与 ISR
| 概念 | 含义 |
|---|---|
| AR(Assigned Replicas) | 分配的所有副本 |
| ISR(In-Sync Replicas) | 当前与 leader 保持同步的副本 |
| OSR(Out-of-Sync Replicas) | 落后太多的副本 |
| LEO(Log End Offset) | 每个副本写入位置 |
| HW(High Watermark) | ISR 中最小的 LEO,消费者只能读到 HW 之前 |
判断标准:Isr 长度 ≥ min.insync.replicas 才能写入(acks=all 时)。
bash
# 只看落后副本
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --describe --under-replicated-partitionstext
Topic: order Partition: 2 Leader: 2 Replicas: 2,3,1 Isr: 2,3分区 2 的副本 1 掉出 ISR。常见原因:网络慢、磁盘慢、broker 重启、GC 停顿。
bash
# 只看不可用分区
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --describe --unavailable-partitions空输出才正常。
unclean.leader.election.enable 决定"ISR 全挂时要不要选一个 OSR 当 leader":
| 值 | 行为 | 结果 |
|---|---|---|
false(推荐) | 分区不可用,等 ISR 恢复 | 高可用性降低,不丢数据 |
true | 拿落后副本顶上 | 分区立即可用,丢数据 |
四、分区数只能加
bash
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 \
--alter --topic order --partitions 12text
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded!加分区会破坏 key 顺序
默认分区器是 hash(key) % 分区数。分区数从 6 变 12,同一个 key 算出来的分区就变了。原来在分区 3 的两条消息,加分区后会分到 3 和 9,顺序无法保证。
所以:
- 按 key 保序的业务,一开始就多留余量(比如按 3 年后的规模定),宁可分区空着。
- 必须加分区时,选择没有同 key 顺序要求的 topic,或接受短暂乱序。
减少分区不支持。必须减只能新建 topic + 用 MirrorMaker 或自己写程序搬数据。
五、保留策略
三种清理手段,可以组合:
ini
# 1. 按时间(默认 7 天)
retention.ms=604800000
# 2. 按大小(每个分区上限)
retention.bytes=107374182400 # 100GB / 分区
# 3. 按 key 压实(只留每个 key 的最新值)
cleanup.policy=compact
# 同时删+压实
cleanup.policy=delete,compact| 策略 | 适用 |
|---|---|
delete | 普通消息流、日志采集 |
compact | 变更日志、状态快照、KTable、__consumer_offsets |
delete,compact | 既要保留最新值,又要清理过期 |
分段与检查频率:
ini
log.segment.bytes=1073741824 # 单个 segment 文件 1GB
log.roll.hours=168 # 或按时间切分
log.retention.check.interval.ms=300000 # 5 分钟检查一次按大小保留不会立刻生效
retention.bytes 是在 segment 粒度上删的。segment 1GB、保留 10GB,那么实际占用会在 10~11GB 之间。如果 log.segment.bytes 设成 10GB,那必须等整个 segment 过期才删,磁盘会瞬间多出 10GB 占用。保留大小要远大于 segment 大小。
compact 的补充配置:
ini
min.cleanable.dirty.ratio=0.5 # 脏数据比例超过 50% 才压实
min.compaction.lag.ms=0 # 消息至少保留多久才允许被压实
delete.retention.ms=86400000 # 墓碑消息(value=null)保留 24h墓碑消息用于告诉消费者"这个 key 删了"。消费者读到 value=null 要自己处理,不能当普通消息。
六、磁盘占用核算
text
磁盘总量 = 日增量 × 副本数 × 保留天数 ÷ 压缩比 × 1.3(余量)例:日增 50GB 原始数据、压缩比 3:1、RF=3、保留 7 天:
text
50 × 3 × 7 ÷ 3 × 1.3 ≈ 455 GB实际查看:
bash
/opt/kafka/bin/kafka-log-dirs.sh --bootstrap-server 10.0.0.31:9092 --describe --topic-list ordertext
{"version":1,
"brokers":[{"broker":1,
"logDirs":[{"logDir":"/data/kafka/logs","error":null,"partitions":[
{"partition":"order-2","size":2147483648,"offsetLag":0,"isFuture":false},
{"partition":"order-0","size":2097152000,"offsetLag":0,"isFuture":false}
]}]}],
"logDirs":[{"logDir":"/data/kafka/logs","error":null,"partitions":[...]}]}size 单位字节。offsetLag 是 future 副本的差值,正常为 0。
按 broker 汇总更方便:
bash
/opt/kafka/bin/kafka-log-dirs.sh --bootstrap-server 10.0.0.31:9092 --describe \
| python3 -c 'import sys,json; d=json.load(sys.stdin); [print(b["broker"], round(sum(p["size"] for x in b["logDirs"] for p in x["partitions"])/1024**3,1), "GB") for b in d["brokers"]]'text
1 42.3 GB
2 41.8 GB
3 43.1 GB三台差异应小于 10%。差太多说明分区分配不均,需要重分配。
七、改配置
bash
# 只看覆盖了默认值的配置
/opt/kafka/bin/kafka-configs.sh --bootstrap-server 10.0.0.31:9092 \
--entity-type topics --entity-name order --describe
# 改
/opt/kafka/bin/kafka-configs.sh --bootstrap-server 10.0.0.31:9092 \
--entity-type topics --entity-name order \
--alter --add-config retention.ms=1209600000,max.message.bytes=2097152
# 恢复默认
/opt/kafka/bin/kafka-configs.sh --bootstrap-server 10.0.0.31:9092 \
--entity-type topics --entity-name order \
--alter --delete-config retention.msmax.message.bytes 要同时看三层:
| 层级 | 配置 | 作用 |
|---|---|---|
| broker | message.max.bytes | broker 能接收的单条上限 |
| topic | max.message.bytes | 该 topic 的上限 |
| 消费者 | fetch.max.bytes / max.partition.fetch.bytes | 单次拉取上限 |
只在 topic 上放大没用,broker 那一层先会拒。报错是 RecordTooLargeException 或 MESSAGE_TOO_LARGE。
八、数据倾斜
bash
/opt/kafka/bin/kafka-log-dirs.sh --bootstrap-server 10.0.0.31:9092 --describe \
| python3 -c 'import sys,json; d=json.load(sys.stdin); [print(p["partition"], round(p["size"]/1024**2,1), "MB") for b in d["brokers"] for x in b["logDirs"] for p in x["partitions"] if p["partition"].startswith("order-")]' | sorttext
order-0 5120.3 MB
order-1 4980.1 MB
order-2 10240.7 MB
order-3 5010.9 MB
order-4 10512.4 MB
order-5 4990.2 MB2 和 4 是其它分区的两倍。三个原因:
- key 太少:只有 3 个大客户,按客户 ID 做 key → 3 个分区吃满,其余空。
- key 设计反了:用"城市"这种低基数维度。
- 没有 key:3.x 以前是轮询,没问题;自定义分区器可能写错。
解法:
- key 加盐:
key = userId + "#" + (random % 4),消费端再聚合。 - 换高基数 key(orderId 而不是 tenantId)。
- 自定义分区器按权重分配。
九、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| 消费者有几个一直空闲 | 消费者实例数 > 分区数 | 扩分区或减消费者 |
| 加分区后同一 key 顺序乱了 | hash(key) % 分区数 变了 | 提前留余量;或改成不分区的保序方案 |
Isr 长期少于 Replicas | 副本拉取慢/网络差/磁盘满 | 查 under-replicated-partitions,看 broker 日志 |
| 磁盘突然涨满 | retention.bytes 未设且 retention.ms 很大 | 设上限,或调小 segment 让删除更及时 |
| 删了 topic 磁盘没释放 | 有消费者还在读(延迟删除) | 等 delete.topic 完成,或查 log.dirs 残留 |
RecordTooLargeException | 只改了 topic 的 max.message.bytes | 同时改 broker 的 message.max.bytes |
建 topic 报 replication factor larger than available brokers | broker 数不足 | 补 broker 或降 RF |
| 三台磁盘差 30% | 分区分配不均 | kafka-reassign-partitions 重分配 |