Kafka 集群安装与运维实战:消费组排查、Offset 重置与副本重分配
Kafka 的日常运维里,真正让人头疼的往往不是「怎么启动」,而是三件事:消费组积压了怎么定位、位移(Offset)乱了怎么重置、__consumer_offsets 这种内部主题副本数不够怎么改。这三件事各自都有坑,而且坑基本都藏在客户端工具的版本差异和参数语义里。
本文按「安装 → 配置 → 使用 → 排查」的顺序,把一套 3 节点 Kafka 集群从部署到应急处理的完整操作整理成一份可以照着敲的手册。文中所有主机名、IP 都做了脱敏(用 kafka1 / kafka2 / kafka3 代替),实际执行时替换成自己的地址即可。
一、速查:本文覆盖的运维场景
| 场景 | 关键命令 | 所在章节 |
|---|---|---|
| 集群部署与参数调整 | kafka-server-start.sh |
二 |
| 主题查看与创建 | kafka-topics.sh |
三 |
| 消费组积压排查 | kafka-consumer-groups.sh --describe |
四 |
| 重置消费位移 | kafka-consumer-groups.sh --reset-offsets |
五 |
| 内部主题副本数调整 | kafka-reassign-partitions.sh |
六 |
| 数据保留与磁盘治理 | log.retention.* |
七 |
| 常见报错定位 | ------ | 八 |
二、集群安装与核心配置
1、分发安装包
集群规模上来以后逐台 scp 很费劲,用 SaltStack 之类的批量工具下发更省事;没有批量工具时把下面每条命令逐台执行,效果一样。
bash
# 批量分发并解压安装包
salt -N 'kafka' cp.get_file salt://kafka_2.12-2.2.1.tgz /data/kafka_2.12-2.2.1.tgz
salt -N 'kafka' cmd.run 'cd /data && tar xf kafka_2.12-2.2.1.tgz'
# 日志目录从默认的 /tmp 改到数据盘
sed -i 's#log.dirs=/tmp/kafka-logs#log.dirs=/data/kafka_2.12-2.2.1/logs#g' \
/data/kafka_2.12-2.2.1/config/server.properties
# 创建日志目录
mkdir -p /data/kafka_2.12-2.2.1/logs
# 指向 ZooKeeper 集群
sed -i 's#zookeeper.connect=localhost:2181#zookeeper.connect=kafka1:2181,kafka2:2181,kafka3:2181#g' \
/data/kafka_2.12-2.2.1/config/server.properties
# broker.id 必须每台不同,手工确认
vi /data/kafka_2.12-2.2.1/config/server.properties
# 后台启动
/data/kafka_2.12-2.2.1/bin/kafka-server-start.sh -daemon \
/data/kafka_2.12-2.2.1/config/server.properties
2、server.properties 关键参数
| 参数 | 建议值 | 说明 |
|---|---|---|
broker.id |
0 / 1 / 2 | 集群内唯一,改动后数据目录会错乱,务必先规划 |
log.dirs |
/data/kafka/logs |
生产环境千万别留在 /tmp,重启即丢 |
zookeeper.connect |
kafka1:2181,kafka2:2181,kafka3:2181 |
老架构依赖 ZK,新架构见下方提示 |
num.partitions |
3 | 新建主题的默认分区数 |
default.replication.factor |
3 | 新建主题的默认副本数,集群只有 1 台时创建主题会直接失败 |
offsets.topic.replication.factor |
3 | __consumer_offsets 的副本数,只在首次创建时生效 |
transaction.state.log.replication.factor |
3 | 事务主题副本数 |
transaction.state.log.min.isr |
2 | 事务写入要求的最小同步副本数 |
message.max.bytes |
20971520 | 单条消息上限,默认 1 MB;调大后服务端和客户端都要改 |
架构提示 :Kafka 2.8 起引入 KRaft(去 ZooKeeper)模式,3.3 之后达到生产可用,4.0 已彻底移除 ZooKeeper。新建集群建议直接用 KRaft ,可以少维护一套 ZK;本文仍以经典的 ZK 架构讲解,因为存量集群大多是这一套,而且下面第六节的
__consumer_offsets处理方式两者完全一致。注意
offsets.topic.replication.factor这类参数只在主题第一次被创建时生效 。事后改配置文件重启,__consumer_offsets的副本数并不会跟着变------这正是下一节要用重分配工具的原因。
3、启动后自检
bash
# 确认 broker 已注册
zookeeper-shell.sh kafka1:2181 ls /brokers/ids
# 列出集群内所有主题
kafka-topics.sh --bootstrap-server kafka1:9092 --list
三、日常运维命令速查
| 用途 | 命令 |
|---|---|
| 列出全部主题 | kafka-topics.sh --bootstrap-server kafka1:9092 --list |
| 查看主题详情 | kafka-topics.sh --bootstrap-server kafka1:9092 --describe --topic test-topic |
| 创建主题 | kafka-topics.sh --bootstrap-server kafka1:9092 --create --topic test-topic --partitions 6 --replication-factor 3 |
| 增加分区 | kafka-topics.sh --bootstrap-server kafka1:9092 --alter --topic test-topic --partitions 12 |
| 列出消费组 | kafka-consumer-groups.sh --bootstrap-server kafka1:9092 --list |
| 查看消费组详情 | kafka-consumer-groups.sh --bootstrap-server kafka1:9092 --group apigw-group1 --describe |
| 列出所有消费组详情 | kafka-consumer-groups.sh --bootstrap-server kafka1:9092 --all-groups --describe |
| 查看主题数据 | kafka-console-consumer.sh --bootstrap-server kafka1:9092 --topic test-topic --from-beginning |
| 生产测试数据 | kafka-console-producer.sh --bootstrap-server kafka1:9092 --topic test-topic |
几个使用要点:
- 主题分区数只能增加,不能减少。想减少只能新建主题再迁移数据,规划时留足余量。
kafka-console-consumer.sh --from-beginning会把整个主题从头拉一遍。生产环境主题数据量大时不要执行 ,会瞬间打满网络和磁盘 IO,需要抽样确认用--max-messages限制条数。- 主题、消费组数量一多,
--describe的输出会很长,配合grep/awk过滤更实用。
四、消费组积压排查
1、读懂 --describe 的输出
bash
kafka-consumer-groups.sh --bootstrap-server kafka1:9092 --group apigw-group1 --describe
| 字段 | 含义 |
|---|---|
GROUP |
消费组名 |
TOPIC |
主题名 |
PARTITION |
分区号 |
CURRENT-OFFSET |
该分区当前已提交的位移 |
LOG-END-OFFSET |
该分区最新消息的位移(可理解为「总共有多少条」) |
LAG |
积压条数,等于 LOG-END-OFFSET - CURRENT-OFFSET |
CONSUMER-ID |
正在消费该分区的消费者实例,- 表示没有消费者在消费 |
判断逻辑很直接:
- 某分区
CONSUMER-ID为-且LAG持续增长 → 没有消费者认领这个分区,通常是实例挂了或消费组一直卡在重平衡; LAG平稳但不下降 → 消费速度追不上生产速度,需要扩容消费者或优化处理逻辑;LAG来回跳变 → 多半是在反复重平衡。
2、快速筛出有积压的组
bash
kafka-consumer-groups.sh --bootstrap-server kafka1:9092 --all-groups --describe \
| awk 'NR==1 || $6>0' | sort -k6 -nr | head -30
--all-groups 输出中 GROUP 是第 1 列,LAG 是第 6 列,所以 $6>0 就能过滤掉全部消费完毕的分区。
3、几个容易被忽略的约束
- 同一消费组内,消费者实例数超过分区数就是浪费。多出来的实例会一直空转,日志上看不到任何错误,但扩容完全没有效果。遇到这种情况要先加分区数。
- 消费者处理太慢导致被踢出组,看的是
max.poll.interval.ms(默认 5 分钟);心跳超时看session.timeout.ms。这两个参数别混。 - 消费组「正在重平衡」时执行任何 Offset 操作都会失败,见下一节。
五、Offset 重置
位移重置是事故处理里的高频操作,也是最容易出事的一个:执行前一定要先 --dry-run 看一眼。
1、前置条件
| 条件 | 说明 |
|---|---|
| 消费组必须处于 inactive 状态 | 组内还有消费者在线时会报 Assignments can only be reset if the group ... is inactive,必须先停掉全部消费者实例 |
用 --bootstrap-server 而不是 --zookeeper |
--zookeeper 从 2.5 起弃用,4.0 已移除,新命令一律走 bootstrap |
先 --dry-run 再 --execute |
--dry-run 只打印即将变更的结果,不会真正提交 |
2、标准流程
bash
# 第一步:预览(不执行)
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--group apigw-group1 \
--reset-offsets \
--to-earliest \
--topic test-topic-a1 \
--dry-run
# 第二步:确认无误后执行
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--group apigw-group1 \
--reset-offsets \
--to-earliest \
--topic test-topic-a1 \
--execute
常用重置策略:
| 参数 | 效果 |
|---|---|
--to-earliest |
回到最早可用位移,相当于「全部重新消费一遍」 |
--to-latest |
跳到最新位移,相当于「丢弃全部积压」 |
--to-offset 100 |
重置到指定位移,适合对齐到某个业务时间点 |
--to-datetime 2026-09-01T00:00:00.000 |
按时间点重置,格式为 ISO8601 |
--shift-by -1000 |
在当前位移上回退 1000 条,适合小范围重放 |
--from-file offsets.csv |
从文件批量导入位移,适合跨集群搬迁 |
风险提示 :
--to-latest会让积压数据永久跳过,--to-earliest会触发全量重放,对下游数据库的压力可能远大于预期。执行前先确认下游能不能承受重复消费,幂等没做好就不要动。
3、服务端与客户端版本不一致的情况
老集群(例如 HDP 2.6.4 自带的 Kafka 0.10)的服务端可能不支持新参数,但客户端工具是可以「借用」的------客户端不需要启动,只需要有二进制包:
bash
# 用旧版本客户端查询(兼容老服务端)
cd /usr/hdp/2.6.4.0-91/kafka/bin/
kafka-consumer-groups.sh --bootstrap-server kafka1:9092 --list
kafka-consumer-groups.sh --bootstrap-server kafka1:9092 --group apigw-groupv2 --describe
# 用新版本客户端执行重置(工具本身不做版本校验)
cd /data/opt/kafka_2.12-2.2.1/bin
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--group apigw-group99 \
--reset-offsets \
--to-offset 100 \
--topic test-topic-a1 \
--execute
六、修改 __consumer_offsets 副本数
__consumer_offsets 是 Kafka 存放消费者位移的内部主题,默认 50 个分区、cleanup.policy=compact。它的默认副本数由 offsets.topic.replication.factor 决定------如果集群刚建起来时只有 1 台 broker,这个主题就会以单副本创建,之后多加 broker 也不会自动补副本。
单副本的后果很严重:承载该分区的 broker 一挂,消费组的位移就不可读,整个组卡死无法重平衡。而由于 offsets.topic.replication.factor 事后修改无效,只能通过分区重分配来补救。
1、修改 Kafka 配置
改 server.properties,配置变更后需要重启:
properties
default.replication.factor=2
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
2、创建待迁移主题列表
创建 topics.json:
json
{
"topics": [
{
"topic": "__consumer_offsets"
}
],
"version": 1
}
3、生成分区重新分配方案
bash
kafka-reassign-partitions.sh \
--zookeeper kafka1:2181,kafka2:2181,kafka3:2181 \
--topics-to-move-json-file topics.json \
--broker-list "0,1,2" \
--generate | awk 'END {print}' | jq '
def rotate(arr; n):
arr[n:] + arr[:n];
.partitions |= map(
.replicas = rotate([0,1,2]; (.partition % 3)) |
.log_dirs = ["any", "any", "any"]
)
' > reassignment-plan.json
这里做了两件事:--generate 会输出「当前分配」和「建议分配」两段 JSON,awk 'END {print}' 取最后一行(即建议方案);后面用 jq 把副本列表按分区号轮转,让副本均匀落在 3 台 broker 上,而不是全部堆在 0,1,2 这个固定顺序上。
生成后务必人工检查 reassignment-plan.json 是否符合预期:
bash
less reassignment-plan.json
4、执行重新分配
bash
kafka-reassign-partitions.sh \
--zookeeper kafka1:2181,kafka2:2181,kafka3:2181 \
--reassignment-json-file reassignment-plan.json \
--execute
性能提示 :重分配本质是把分区数据在节点间整体复制一遍。数据量大时务必加
--throttle 50000000(单位字节/秒,即 50 MB/s)限速,否则容易把磁盘 IO 和网络打满,在业务高峰引发连锁故障。限速后可通过--verify观察进度,完成后记得用--throttle 0或再执行一次解除限速。
5、验证分配进度
bash
kafka-reassign-partitions.sh \
--zookeeper kafka1:2181,kafka2:2181,kafka3:2181 \
--reassignment-json-file reassignment-plan.json \
--verify
输出逐分区显示状态,全部为 completed successfully 即表示完成。中途中断不会回滚,重新执行 --execute 会从断点继续。
6、检查最终副本数
bash
kafka-topics.sh \
--bootstrap-server kafka1:9092,kafka2:9092,kafka3:9092 \
--describe \
--topic __consumer_offsets
预期输出:
text
Topic:__consumer_offsets PartitionCount:50 ReplicationFactor:3 Configs:compression.type=producer,cleanup.policy=compact,segment.bytes=104857600
Topic: __consumer_offsets Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: __consumer_offsets Partition: 1 Leader: 1 Replicas: 0,1,2 Isr: 0,1,2
Topic: __consumer_offsets Partition: 2 Leader: 2 Replicas: 0,1,2 Isr: 0,1,2
关键看三列:ReplicationFactor 是否变成 3、每个分区的 Replicas 是否为 3 个副本、Isr 是否与 Replicas 一致。Isr 少于 Replicas 说明有副本没追上,需要继续观察。
7、Leader 重新选举(可选)
重分配完成后,Leader 分布可能仍然不均衡。触发一次优先副本选举即可让 Leader 回到 Replicas 列表的第一位:
bash
# 生成分区列表
kafka-topics.sh \
--bootstrap-server kafka1:9092,kafka2:9092,kafka3:9092 \
--describe \
--topic __consumer_offsets | \
grep "Partition:" | awk '{print $4}' | \
jq -Rn '[inputs|{"topic":"__consumer_offsets","partition":(tonumber)}] | {"partitions": .}' \
> partitions.json
# 执行优先副本选举
kafka-preferred-replica-election.sh \
--bootstrap-server kafka1:9092,kafka2:9092,kafka3:9092 \
--path-to-json-file partitions.json
再次 --describe 确认 Leader 是否均匀分布。注意 kafka-preferred-replica-election.sh 在 2.4 之后也有对应的 --zookeeper 与 --bootstrap-server 两种形态,新版本只能用后者。
七、日志清理与数据保留
Kafka 的数据不会自动消失,磁盘写满是最常见的生产事故之一。在 server.properties 中配置:
properties
# 开启日志清理
log.cleaner.enable=true
# 日志保留小时数,默认 7 天(168 小时)
log.retention.hours=168
# 日志保留总字节数,超过会清理
log.retention.bytes=419430400
# 每个日志分段文件大小
log.segment.bytes=1073741824
# 日志清理检查间隔
log.retention.check.interval.ms=300000
几个关键语义,理解错了很容易误判:
log.retention.bytes限制的是每个分区的日志总量,不是整台 broker 的总量。50 个分区 × 400 MB = 20 GB,算容量时要按分区数乘。- 实际生效的保留策略是「时间」和「大小」取先满足的那个,任一条件触发就会删除分段。
- 删除的最小单位是分段(segment) ,不是单条消息。所以
log.segment.bytes设得太大,会导致log.retention.hours明显滞后生效------一个还没写满的活跃分段不会被删。 cleanup.policy=delete是按保留策略删除;compact是按 key 保留最新值。__consumer_offsets用的是compact,不要随意改动。log.retention.check.interval.ms决定清理任务的扫描频率,默认 5 分钟。磁盘告警时把值调小可以更快释放空间,但会增加 IO 压力。
八、常见故障排查
| 现象 | 可能原因 | 处理 |
|---|---|---|
消费组长时间卡在 PreparingRebalance |
消费者处理超时被踢出组、分区数过多导致重平衡慢 | 调大 group.initial.rebalance.delay.ms、session.timeout.ms;核对 max.poll.interval.ms 是否小于单批处理耗时 |
消费者反复重连、日志刷 Rebalance |
心跳超时,或一次 poll 拉取数据过多 |
调小 max.poll.records,调大 session.timeout.ms |
| ISR 频繁收缩、副本同步滞后 | 磁盘 IO 瓶颈、网络抖动、单副本数据量过大 | 检查磁盘 iostat,评估调大 replica.lag.time.max.ms |
报 RecordTooLargeException |
单条消息超过上限 | 三处都要放开:服务端 message.max.bytes、broker 间 replica.fetch.max.bytes、消费者 fetch.message.max.bytes |
创建主题报 replication factor: 3 larger than available brokers: 1 |
集群可用 broker 数少于副本数 | 补齐 broker,或按实际节点数指定 --replication-factor |
__consumer_offsets 单副本告警 |
建库时集群只有 1 台 broker | 按第六节做分区重分配 |
| 加了消费者但积压不降 | 实例数已超过分区数 | 先扩分区,再扩消费者实例 |
提示 Unrecognized option: --zookeeper |
工具版本已移除 ZK 参数 | 统一改用 --bootstrap-server |
| 磁盘使用率持续上涨不释放 | 活跃分段未写满,或保留策略单位理解有误 | 核对 log.segment.bytes 与 log.retention.bytes 的分区级语义 |
总结
把上面这些串起来,Kafka 运维其实可以归纳成四条主线:
- 部署阶段就要把副本数定对 。
offsets.topic.replication.factor这种参数只在主题首次创建时生效,等事后发现__consumer_offsets是单副本,补救成本是整整一节的分区重分配流程。新集群建议直接上 KRaft,省掉 ZooKeeper 这一层。 - 消费组问题先看 LAG 。
--all-groups --describe配合awk过滤,几秒钟就能判断是没消费者、消费太慢,还是卡在重平衡,再决定是扩容、调参还是重置位移。 - 一切破坏性操作先
--dry-run。位移重置前必须停掉消费者,--to-latest会永久跳过数据,--to-earliest会全量重放,下游不幂等就别做。 - 重分配一定要限速 。
--throttle加上去,慢一点没关系,把生产集群打挂才是真的麻烦。
磁盘和副本这两件事属于「不处理就一定会出事」的类型,建议给 log.retention.bytes 和 __consumer_offsets 的副本状态都加上监控告警,别等到故障了再翻文档。