
消息队列 Kafka 用得越久,磁盘水位告警来得越勤:日志类 topic 日增几十 GB、大分区全挤在一块盘上、系统盘被 server.log 慢慢吃光。本文把生产环境沉淀的四类存储治理手段整理成一套完整 SOP------保留策略让数据"删得快"、数据目录扩容让集群"装得下"、分区跨盘迁移让磁盘"搬得平"、整体换盘让硬件"换得走",全部支持滚动操作不停机,附踩坑清单。命令在 Kafka 2.5 与 3.9 集群上均验证过。
📚 相关系列:《Kafka 集群开启 SASL 认证实战(一):内置 ZooKeeper 场景》《Kafka 2.8→3.9.1 跨大版本升级实战》《云原生日志采集与检索架构实战:Kafka + Elasticsearch》------本文专注存储与磁盘维度,认证与升级问题看前两篇。
治理全景:先分清四种场景
收到磁盘告警,先判断属于哪一类,再对症下药:
| 现象 | 根因 | 对应方案 | 是否停机 |
|---|---|---|---|
| 磁盘水位持续上涨,历史数据没人消费 | 保留策略缺失或太长 | 方案一:保留策略 | 否 |
| 新增磁盘已挂载,但 Kafka 用不上 | log.dirs 只配了旧目录 | 方案二:目录扩容 | 滚动重启 |
| 某块盘快满,其他盘很空 | 分区分布不均 | 方案三:分区跨盘迁移 | 否 |
| 磁盘要下线/换 SSD/迁移存储 | 硬件变更 | 方案四:整体换盘 | 滚动迁移 |
方案一:保留策略------让 Kafka 自动删数据
Kafka 清理策略分两个级别:全局配置 作用于所有主题(server.properties),主题级配置只作用于自身并覆盖全局。日增量大的日志类消息队列,两个级别通常都要配。
1.1 全局配置
修改每个节点的 server.properties,然后滚动重启:
properties
# 数据最大保留小时数(示例 7 天,默认也是 7 天)
log.retention.hours=168
# 数据最大保留字节数(示例 10GB)
log.retention.bytes=10737418240
# 每个节点的保留检查间隔(默认 5 分钟)
log.retention.check.interval.ms=300000
语义是"或 "的关系:分区内数据的时间超过 7 天,或者分区总大小超过 10GB,满足任一条件就会触发删除------所以流量大的分区实际按大小删,流量小的分区按时间删。
1.2 主题级配置(不用重启)
全局配置改一次要滚动重启整个集群,主题级的调整用 kafka-configs.sh 动态生效,单机或集群执行一次即可:
bash
# 设置某个主题数据最大保留毫秒数(示例 24 小时)
./bin/kafka-configs.sh --alter \
--add-config retention.ms=86400000 \
--bootstrap-server=192.168.10.31:9092 \
--topic app-log \
--command-config ./config/producer.properties
# 设置某个主题数据最大保留字节数(示例 10GB)
./bin/kafka-configs.sh --alter \
--add-config retention.bytes=10737418240 \
--bootstrap-server=192.168.10.31:9092 \
--topic app-log \
--command-config ./config/producer.properties
--command-config 指向带认证信息的配置文件,开了 SASL 的集群必带(怎么生成这个文件见系列第一篇);没开认证的集群可以省略。
1.3 验证配置是否生效
bash
./bin/kafka-configs.sh --bootstrap-server 192.168.10.31:9092 \
--describe --all --topic app-log \
--command-config ./config/producer.properties | grep "retention"
输出中 retention.ms=86400000 即 24 小时、retention.bytes=10737418240 即 10GB,说明主题级覆盖已生效。
1.4 三个高频踩坑
坑一:retention.bytes 是"每分区"上限,不是主题总量。 topic 有 6 个分区、每分区限 10GB,主题实际能囤 60GB。想控制主题总量,要用 每分区上限 = 主题期望总量 / 分区数 反推。
坑二:设了保留 1 小时,数据却活了 7 天。 删除的最小单位是日志段(segment)文件,不是单条消息 ,且活跃段永远不删------只有 roll 出新段后,旧段才可能过期。低流量 topic 长时间写不满一个段(默认 1GB),时间早就"过期"了却一直删不掉。解法是配小段,让段滚动起来:
properties
# 段文件大小(低流量主题调小,如 128MB)
log.roll.bytes=134217728
# 或按时间滚动段(如 15 分钟)
log.roll.ms=900000
坑三:改完 retention 磁盘没立刻下降。 过期段先被改名为 .deleted,默认再等 log.segment.delete.delay.ms(60 秒)才真正从文件系统删除;同时受 log.retention.check.interval.ms 检查周期影响,最长延迟 = 检查间隔 + 删除延迟。生产上"改完 10 分钟内磁盘下降"都算正常,别急着反复改参数。
方案二:数据目录在线扩容------log.dirs 追加与运行日志分离
新磁盘挂载好了,Kafka 不会自动用上:分区只会写进 log.dirs 里声明的目录。扩容分三步。
2.1 新目录准备
bash
mkdir -p /data/kafka/datalogs
chown -R kafka:kafka /data/kafka
属主必须是运行 Kafka 的系统用户,否则 broker 启动后写不了日志段,直接报 IO 异常。
2.2 修改 log.dirs:逗号追加,不是覆盖
properties
# 修改前
log.dirs=/opt/bigdata/kafka/kafka-logs-1
# 修改后
log.dirs=/opt/bigdata/kafka/kafka-logs-1,/data/kafka/datalogs
注意是追加。手滑写成覆盖,重启后 broker 找不到旧目录里的分区副本,会触发全量副本同步甚至脱机。
Kafka 对多目录的分配规则:新分区优先分给"分区数最少"的目录 。所以扩容后新数据会自动流向新盘,但已存在的分区不会挪窝------老分区还压在旧盘上,这是方案三要解决的问题。
2.3 顺手分离运行日志目录
数据目录管的是消息日志段,Kafka 自身的运行日志(server.log 等)默认写在安装目录的 logs/ 下,一样会吃系统盘。修改 bin/kafka-run-class.sh:
bash
# Log directory to use
# 在这行注释下面插入
LOG_DIR=/data/kafka/logs
if [ "x$LOG_DIR" = "x" ]; then
LOG_DIR="$base_dir/logs"
fi
2.4 滚动重启
逐台重启,每台稳定后再动下一台:
bash
su - kafka
export KAFKA_HOME=/opt/bigdata/kafka/kafka_2.13-3.9.1
$KAFKA_HOME/bin/kafka-server-stop.sh
# 等待 30 秒让进程退出干净
$KAFKA_HOME/bin/kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties
每台重启后观察两件事:/data/kafka/datalogs 下有无新增文件(说明新目录被识别),以及消费端 lag 是否回落(说明副本已追平)。一般 3 分钟左右稳定,再迁下一台。
方案三:分区跨盘迁移------kafka-reassign-partitions.sh 的 log_dirs 用法
目录扩容只解决"以后的新分区",已存在的大分区还在旧盘上越涨越快。kafka-reassign-partitions.sh 除了重排副本,还能只挪目录、不动副本拓扑 ------秘诀在 JSON 里的 log_dirs 字段。
3.1 查看现状
bash
bin/kafka-topics.sh --bootstrap-server 192.168.10.31:9092 \
--describe --topic task-log \
--command-config ./config/producer.properties
输出里每行一个分区:Partition: 0 Leader: 2 Replicas: 2,0 Isr: 2,0。记下每个分区的副本分布。
3.2 编写迁移 JSON
把 task-log 的各分区都搬到 /data/kafka/datalogs(replicas 原样照抄,只改 log_dirs):
json
{
"version": 1,
"partitions": [
{"topic": "task-log", "partition": 0, "replicas": [2,0], "log_dirs": ["/data/kafka/datalogs","/data/kafka/datalogs"]},
{"topic": "task-log", "partition": 1, "replicas": [1,2], "log_dirs": ["/data/kafka/datalogs","/data/kafka/datalogs"]},
{"topic": "task-log", "partition": 2, "replicas": [0,1], "log_dirs": ["/data/kafka/datalogs","/data/kafka/datalogs"]}
]
}
三个细节:
- replicas 与 log_dirs 一一对应、数量必须相等:第 N 个目录就是第 N 个副本要去的地方;
- replicas 数组照抄
--describe查出来的现有值,别顺手改顺序------第一个元素是首选 Leader; - 不想指定某个副本的目录时可以写
"any",Kafka 自行决定。
3.3 执行与验证
bash
# 执行迁移
bin/kafka-reassign-partitions.sh \
--bootstrap-server 192.168.10.31:9092 \
--command-config ./config/producer.properties \
--execute --reassignment-json-file migrate-tasklog.json
# 查看进度,输出 successfully 字样即完成
bin/kafka-reassign-partitions.sh \
--bootstrap-server 192.168.10.31:9092 \
--command-config ./config/producer.properties \
--verify --reassignment-json-file migrate-tasklog.json
迁移是搬文件 + 改元数据,数据量大时耗时取决于磁盘 IO。操作前建议先停掉该主题相关的生产消费应用,搬完再启动,避免迁移期间 IO 争抢影响业务(不停机也能做,只是慢)。
3.4 同族操作:扩分区与扩副本
同一套工具还能干另外两件事,一并记下。
扩分区(只能从小往大,往小改直接报错):
bash
bin/kafka-topics.sh --bootstrap-server 192.168.10.31:9092 \
--alter --partitions 6 --topic task-log \
--command-config ./config/producer.properties
注意:新增分区后,按 key 发送的老消息仍走旧分区,只有新 key 的路由会散到新分区,依赖 key 有序的消费逻辑要评估影响。
扩副本 :写一份不带 log_dirs 意图的 reassignment JSON(replicas 写成目标布局,log_dirs 全填 "any"),同样 --execute。两个实测细节:一是副本要均匀摊到各 broker,别所有分区都压同一组机器;二是执行后迟迟不同步(Isr 迟迟不齐)时,重启目标机器上的 Kafka 节点通常立刻开始同步,测试环境反复验证过。
方案四:主题重建------删错了数据格式时的兜底
有时不是磁盘问题,而是 topic 的数据格式/分区数彻底错了,需要删掉重建。
4.1 前提:打开删除开关
删除前确认 server.properties 里配了 delete.topic.enable=true(集群每台都要配),否则删除只是"标记",不做真删:
properties
delete.topic.enable=true
4.2 删除与重建
bash
# 删除
./bin/kafka-topics.sh --delete --bootstrap-server 192.168.10.31:9092 \
--topic demo-topic --command-config ./config/producer.properties
# 重建(分区数、副本数按新规划来)
./bin/kafka-topics.sh --bootstrap-server 192.168.10.31:9092 \
--create --partitions 6 --replication-factor 2 \
--topic demo-topic \
--command-config ./config/producer.properties
删除成功的返回是 Topic demo-topic is marked for deletion------注意措辞是"标记",副本文件在后台异步清掉,--list 里还会残留一会儿,属正常。
删除前先停该主题的生产与消费端 :一是避免删除瞬间客户端报错;二是很多集群开着 auto.create.topics.enable,生产端一不留神就会把刚删掉的 topic 又自动建出来,带着默认分区数"诈尸",前功尽弃。另外两个红线:__consumer_offsets 这类内部 topic 永远不要删;老版本集群(2.x 前)删除走 ZooKeeper 元数据 + 重启 broker,节奏完全不同,别照搬本文。
方案五:整体换盘不停机------逐台滚动迁移
硬件更换、系统盘换数据盘、迁移到 SSD,都要把整个 Kafka 安装目录搬到新磁盘。核心思路:一台一台来,迁移完一台、副本同步追平,再动下一台,集群全程可用。
以 Kafka 安装在 /opt/bigdata/kafka、要迁到 /data 为例:
bash
# ① 停本机 broker
su - kafka
$KAFKA_HOME/bin/kafka-server-stop.sh
# ② 移动安装目录(root 执行)
mv /opt/bigdata/kafka /data/.
# ③ 批量替换路径引用:配置、启动脚本、环境变量三处
cd /data/kafka/kafka_2.13-3.9.1
sed -i 's#/opt/bigdata/kafka#/data/kafka#g' config/server.properties
sed -i 's#/opt/bigdata/kafka#/data/kafka#g' bin/kafka-run-class.sh
# ④ 启动
export KAFKA_HOME=/data/kafka/kafka_2.13-3.9.1
$KAFKA_HOME/bin/kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties
验证两件事再迁下一台:
bash
# 本机 broker 已入列 Isr
bin/kafka-topics.sh --bootstrap-server 192.168.10.32:9092 --describe \
--topic task-log --command-config ./config/producer.properties
# 消费端有最新数据进来
bin/kafka-console-consumer.sh --bootstrap-server 192.168.10.32:9092 \
--topic task-log --from-beginning --max-messages 1 \
--command-config ./config/producer.properties
踩坑提醒 :路径引用不止 server.properties 一处------log.dirs、KAFKA_HOME 环境变量(systemd unit、profile 里写死的)、以及脚本里写死的绝对路径都要过一遍,grep -r '/opt/bigdata/kafka' /data/kafka 确认清零再启动,否则 broker 起来找不到副本目录,会被误判为数据丢失。
顺手加固:给 JMX 端口加认证
治理磁盘时经常顺手开 JMX 端口做监控,默认 JMX 无认证,等于把集群内部指标裸奔在内网。加认证三步:
bash
cd /data/kafka/kafka_2.13-3.9.1/config
# ① 密码文件:格式为 <用户名> <密码>
echo "monitor Jmx@Monitor123" > jmx.password
chmod 600 jmx.password && chown kafka:kafka jmx.password
# ② 访问控制文件:readonly 只读
echo "monitor readonly" > jmxremote.access
chmod 600 jmxremote.access && chown kafka:kafka jmxremote.access
在 bin/kafka-server-start.sh 倒数第二行插入:
bash
export JMX_PORT="10992"
export KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote=true \
-Djava.rmi.server.hostname=192.168.10.31 \
-Dcom.sun.management.jmxremote.authenticate=true \
-Dcom.sun.management.jmxremote.password.file=/data/kafka/kafka_2.13-3.9.1/config/jmx.password \
-Dcom.sun.management.jmxremote.access.file=/data/kafka/kafka_2.13-3.9.1/config/jmxremote.access \
-Dcom.sun.management.jmxremote.ssl=false"
chmod 600 是硬性要求 ------JMX 检测到密码文件权限过宽会直接拒绝启动。重启后用 JConsole 连 service:jmx:rmi:///jndi/rmi://192.168.10.31:10992/jmxrmi,输入账号密码能看到 MBean 即成功。集群其他节点照做。
总结:一张决策表 + 避坑清单
| 场景 | 首选方案 | 关键命令 |
|---|---|---|
| 历史数据太多 | 保留策略 | kafka-configs.sh --alter --add-config retention.ms=... |
| 新盘没被利用 | 目录扩容 | log.dirs 追加 + 滚动重启 |
| 分区分布不均 | 跨盘迁移 | kafka-reassign-partitions.sh --execute + log_dirs |
| topic 建错了 | 删除重建 | delete.topic.enable=true + --delete |
| 换硬盘/换存储 | 整体迁移 | 逐台 stop→mv→sed→start |
最后过一遍避坑清单,都是生产上真踩过的:
retention.bytes按分区算,主题总量要乘分区数;- 活跃 segment 不删,低流量 topic 记得配
log.roll.bytes/log.roll.ms; - 改完 retention 磁盘下降有分钟级延迟,别反复改;
log.dirs是追加不是覆盖,覆盖 = 副本全量重建;- reassignment JSON 里 replicas 与 log_dirs 数量必须一致;
- 扩分区只增不减,且影响 key 路由有序性;
- 删 topic 前停生产端,防止 auto-create"诈尸";
- 整体换盘后
grep确认旧路径清零再启动; - JMX 密码文件权限必须 600,否则 broker 起不来。
你的 Kafka 集群磁盘治理是靠 retention 一把梭,还是也做过分区级搬迁?评论区聊聊你遇过的磁盘名场面。
操作步骤来自生产环境实测(Kafka 2.5 / 3.9 集群);文中 IP、密码均为示例。