Skip to content

Topic 与分区:数量、副本、保留策略 ​

分区数一旦定错,只能加不能减。加分区又会打乱按 key 的顺序。所以这是 Kafka 里最该一次想清楚的事。

场景 ​

建 topic 前必须回答四个问题:

  1. 目标写入吞吐多少?决定分区数下限。
  2. 未来最多几个消费者并行?决定分区数上限。
  3. 丢一条消息能不能接受?决定副本数与 acks。
  4. 数据保留多久?决定磁盘容量。

一、分区数怎么定 ​

单分区写入吞吐的经验值: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=producer
text
Created topic order.
参数说明
--partitions 66 个分区,消费者组最多 6 个实例并行(多出来的会空闲)
--replication-factor 33 副本,能容忍 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 order
text
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-partitions
text
	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 12
text
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 order
text
{"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.ms

max.message.bytes 要同时看三层:

层级配置作用
brokermessage.max.bytesbroker 能接收的单条上限
topicmax.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-")]' | sort
text
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 MB

2 和 4 是其它分区的两倍。三个原因:

  1. key 太少:只有 3 个大客户,按客户 ID 做 key → 3 个分区吃满,其余空。
  2. key 设计反了:用"城市"这种低基数维度。
  3. 没有 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 brokersbroker 数不足补 broker 或降 RF
三台磁盘差 30%分区分配不均kafka-reassign-partitions 重分配