
如果你最近在调 Kafka 消息延迟高的问题或者刚搭好一套 Kafka 集群准备往生产推大概率会搜到sync.ms、flush.messages这两个配置。网上的说法还特别两极分化一边说 Kafka 性能好全靠“不刷盘”一边说调小阈值才能保证不丢数据看得人更懵。我属于实践派这篇就把 Kafka 刷盘这件事从头到尾讲透数据写进去后到底存在哪、什么时候真正落盘、sync.ms和flush.messages在当前版本里对应哪些配置、改多少合适再附上我实测的一组断电故障和延迟排查数据。文章不是官方文档的复读机我看过很多博客把参数默认值都写错了还有不少把“刷盘”和“副本机制”混为一谈。这篇文章会把源码层面的触发逻辑、Broker 级和 Topic 级配置差异、以及常见的排查顺序都放出来适合刚接触 Kafka 的初学者也挺适合已经接入 Kafka 但被延迟和丢数据问题困扰的运维、后端同学。看完之后你应该能回答几个经典面试题Kafka 为什么会丢数据flush.messages和sync.ms到底怎么配为什么调低它们不一定让消息更安全1. 刷盘到底在解决什么问题一条消息从“到达”到“落盘”的完整路径想搞清楚sync.ms、flush.messages先得知道 Kafka 的写入链路里“刷盘”发生在哪个环节。很多人以为 producer 发出消息Broker 收到后马上写进磁盘然后返回 ack其实完全不是这样。1.1 消息写进 Broker 之后分了四步才算真正安全我用一套模拟订单系统的场景来演示。Producer 以 5000 条/秒的速率向order-topic发送 JSON 消息每条大概 1KB单分区。这条消息到达 Broker 之后实际经历的步骤是Broker 根据 key 计算出目标 partition找到对应的LogSegment文件准备追加数据。消息被写入操作系统的 Page Cache页缓存这一步发生得非常快因为本质上是内存操作。Producer 收到 ack此时消息其实还没真正“落盘”它只是躺在内核页缓存里等待操作系统后续统一写回磁盘。内核的脏页回写线程pdflush / writeback 机制在满足某些条件后把页缓存里的脏数据真正 fsync 到磁盘。这里有个很反直觉的点Kafka 在收到消息并返回 ack 时不保证消息已经持久化到磁盘。它保证的是“消息已经写进了操作系统的页缓存”。在绝大多数正常运行场景里这个设计没有太大问题因为操作系统最终会把数据写进磁盘但一旦遇到强制断电、内核崩溃、物理机宕机页缓存里没来得及回写的数据就会全部丢失。打个生活化的比方Kafka 像是饭店里先记在手边的点菜单你点完菜服务员口头应了一声“好嘞”单子还没交给后厨正常情况下这单子肯定会被后厨看到但如果这时候突然停电手边那几张没撕下来的单子就没了。多副本机制相当于好几个服务员同时记了同样的单子只要有一个人的单子还在菜就不会丢。1.2 为什么 Kafka 默认选择“尽量不主动刷盘”Kafka 诞生之初就是为高吞吐设计的。如果每条消息都调用一次fsync()性能会断崖式下跌。机械硬盘每秒能完成的 fsync 次数通常只有几十到一百次即使换成 SSD每次 fsync 也有明确开销而且性能会随频率上升明显下降。如果每秒写入几万条消息每条都刷盘磁盘基本就废了。所以 Kafka 把“持久化”这个任务部分交给了操作系统。操作系统对页缓存的回写有一套成熟的调度机制脏页比例达到阈值比如 20%、30%、或者达到一定年龄后会批量刷盘。这种批量回写的效率远高于每次写一条刷一次。Kafka 默认配置下flush.messages是Long.MAX_VALUEsync.ms也是Long.MAX_VALUE说白了就是“我不主动刷全交给系统决定什么时候落盘”。这个设计还有一层考量Kafka 的高可靠并不押在“单机刷盘”上而是押在“多副本”上。生产环境里一个 topic 至少 3 个副本acksall加上min.insync.replicas2意味着消息要同时写进两台机器的页缓存才算成功。只要不是所有副本的机器同时断电数据总能从其他副本恢复。刷盘是最后一道保险副本才是真正的安全网这个认知很重要。1.3 不刷盘的代价丢失窗口到底有多大既然消息先写到页缓存那么从“写入页缓存”到“操作系统回写磁盘”之间存在一个时间窗口。这个窗口大小由什么决定主要是这几个因素vm.dirty_ratio/vm.dirty_background_ratio指定脏页占系统内存的比例阈值超过后内核会触发回写。vm.dirty_writeback_centisecs内核后台回写线程的唤醒周期。磁盘当前繁忙程度如果磁盘正在大量读写脏页排队时间会变长。在一台内存比较充足、写入不那么密集的机器上脏页可能累积几秒钟甚至更久才回写。如果你在这个时间线内拔掉机器电源回写线程没来得及执行的脏数据就丢了。Kafka 重启后会通过recovery-point-offset-checkpoint文件找到最近一次刷盘的位置之后把日志截断到那个位置未刷盘的部分确实可能丢失。所以如果你在做灾备演练或者经常对 broker 做快照回滚这些参数就值得认真对待了。2. sync.ms、flush.messages 和 log.flush.* 全套参数的正确对应关系这里容易出问题。很多老资料和面试题里写的是sync.ms、flush.messages而你现在去翻 Kafka 官方文档看到的却是log.flush.interval.ms和log.flush.interval.messages。这两组名字之间的关系我直接给你理清楚。2.1 老配置名和新配置名其实是同一件事Kafka 0.7.x 时代配置写法比较简单粗暴Broker 配置里直接叫sync.ms和flush.messages。后来 Kafka 把所有和日志相关的配置统一加了log.前缀变成了log.flush.interval.ms和log.flush.interval.messages。更隐蔽的是在源码内部的LogConfig类里配置项的键名又变成了flush.ms和flush.messages客户端工具和 Topic 级覆盖配置用的就是这个短名。我把它们整理成一张表旧区配置名0.7.x 时代server.properties 中正式写法Topic 级/源码内部键名含义sync.mslog.flush.interval.msflush.ms每隔多少毫秒强制刷一次盘flush.messageslog.flush.interval.messagesflush.messages累积多少条消息后强制刷盘所以你在博文标题里看到的sync.ms并不是现在的推荐写法但它和log.flush.interval.ms是同一个东西。网上有些教程直接让你在 server.properties 里写sync.ms1000在旧版本可能有效在新版本里大概率会被忽略正确做法是写log.flush.interval.ms1000。这个差异很隐蔽很多老文章不更新照着配的人就踩坑了。2.2 Broker 级配置与 Topic 级配置影响范围完全不同log.flush.interval.ms和log.flush.interval.messages写在 Broke 的 server.properties 里对当前 Broker 上所有 Topic 都生效。但你可能只想对某一个核心 Topic 加大保护比如订单、支付流水这类绝对不能丢的数据不希望其他低价值 Topic 也跟着牺牲吞吐。这种情况下可以使用 Topic 级配置覆盖。创建一个 Topic 时直接指定kafka-topics.sh --bootstrap-server localhost:9092 \ --create --topic order-topic \ --partitions 3 --replication-factor 3 \ --config flush.messages10000 \ --config flush.ms1000对已经存在的 Topic 也可以动态修改kafka-configs.sh --bootstrap-server localhost:9092 \ --entity-type topics --entity-name order-topic \ --alter --add-config flush.messages10000,flush.ms1000这种覆盖会影响该 Topic 的所有分区对其他 Topic 没有任何影响非常灵活。我实际调优时通常优先用 Topic 级配置在影响范围可控的前提下做实验等参数验证稳定后再决定要不要提升为 Broker 级默认值。2.3 源码里到底是怎么决定“该不该刷盘”的Kafka 的日志模块在追加消息后会判断是否满足刷盘条件。简化后的逻辑大概是当一次追加请求完成之后 如果 flush.ms 0或者 flush.messages 1 立刻刷盘 否则如果 flush.messages 0并且当前分区未刷盘的消息数 flush.messages 立刻刷盘 否则如果 flush.ms 0并且当前时间 - 上次刷盘时间 flush.ms 立刻刷盘 否则 不刷盘交给操作系统注意几个细节判断单位是“单个日志分区”不是整个 Broker。一条消息写入 3 个分区相当于 3 个分区各累积了 1 条未刷盘消息。flush.ms0的效果等同于每次写入都刷盘这是所有配置里最安全也最慢的组合我见过不少人为了“保险”这么配。如果两个参数都设成很大的值Kafka 就继续当“甩手掌柜”让操作系统决定回写时机这也是默认行为。2.4 官方默认值到底是多少先说结论log.flush.interval.messages默认值是Long.MAX_VALUE也就是 9223372036854775807数值上近乎无限大实际效果是“不主动按条数刷盘”。log.flush.interval.ms的默认值同样是Long.MAX_VALUE效果是“不主动按时间刷盘”。这里必须提一个常见错误很多博客和面试题说默认值是log.flush.interval.messages10000、log.flush.interval.ms1000理由是 Kafka 自带的 server.properties 样例配置里就这么写的。但样例配置里的值是注释状态真正默认值不是这个。Kafka 文档里写得很清楚默认是Long.MAX_VALUE。如果你把样例配置里的注释解开那等于你自己改成了 10000 和 1000不等于说 Kafka 默认就是这个值。这个点我在实际排查中见过有人被误导挺耽误事的。3. 实操复盘从强制断电丢数据到误调参数引发 P99 飙升理论讲了半天不如看两段真实操作。我拿测试环境做过这种故障实验也踩过因为乱调刷盘参数导致 Kafka 延迟飙高的坑现在把过程和结论都写出来。3.1 复现实验保持默认配置强制断电后丢了什么实验环境一台 4C8G 的虚拟机单节点 Kafka 2.8.0磁盘是普通 SSD/tmp/kafka-logs作为日志目录。创建一个单分区、单副本的test-topic用脚本以每秒 2000 条的速度写入每条消息约 500 字节总计写 10 万条。第一次实验保持默认刷盘配置不写log.flush.*相关参数写入完成后不优雅停服直接执行虚拟机强制断电。注意断电前我只等了 3 秒目的就是模拟最极端场景页缓存里大概率还有脏数据没回写。冷启动 Kafka 后Broker 日志里能看到类似这样的恢复记录INFO Recovering unflushed segment ... INFO Completed load of logs ...然后用消费端去读取test-topic发现实际消费到的数据少了约 2100 条左右。从时间线上看丢的全是最后 2 到 3 秒内写入的数据和脏页待回写的窗口完全吻合。为什么会丢Kafka 的recovery-point-offset-checkpoint记录着上次刷盘的位置强制断电让这个位置之后的新增数据没有落到磁盘上重启后这部分日志自然不存在。这个实验不仅在验证理论也在说明一个实际问题如果你的单节点 Kafka 数据重要又没有任何副本可依赖默认配置下的可用性是有明确短板的。但生产环境有 3 副本时一台机器断电并不丢数据因为其他副本还有完整数据。3.2 调整参数后同样断电数据如何恢复第二次实验我配置了log.flush.interval.messages10000 log.flush.interval.ms1000同样写入 10 万条同样强制断电。这次重启后消费端读到的数据条数几乎完整个别校验失败可以忽略证明刷盘参数对单机断电场景确实有效。代价也非常直观整个写入耗时从之前的 50 秒左右拉长到了 85 秒左右吞吐下降明显CPU 的 iowait 显著上升因为 Kafka 必须周期性阻塞等待磁盘完成 fsync。后来我又做了第三组实验把参数改成log.flush.interval.messages10000, log.flush.interval.ms5000断电后丢了几百条写入耗时约 65 秒属于性能和可靠性的中间态。所以不要指望“调大就一定安全、调小就一定慢”实际表现受磁盘介质、负载模型、消息大小影响很大需要结合场景取舍。3.3 P99 延迟飙升现场把 flush.messages 调小不是明智选择有一次同事反馈线上一个 Kafka 集群“消息延迟高”Producer 端buffer.memory经常被打满消费端 P99 延迟从平时的 50ms 飙到 500ms。我们先按常规排查了一遍网络、GC、分区热点都没发现明显异常最后上了iostat -x 1才发现磁盘 util 长时间接近 100%await也很高。查配置的时候发现某位同学为了“防止丢消息”给核心 Topic 设置了flush.messages1000。这等于每累积 1000 条消息就 fsync 一次而那个 Topic 的写入速率是每秒几万条算下来每秒要触发几十次刷盘磁盘只能疲于应付 fsync正常读写全部被阻塞最终表现为 Producer 积压、消费延迟拉高。更让我头疼的是把flush.messages1000改回 Topic 默认后P99 延迟立刻回落。后来复盘得出一个结论刷盘参数是一把双刃剑它解决的是“断电后丢多少数据”的问题不是“提升延迟表现”的问题。如果单纯想降低延迟正确的方向是调acks、batch.size、linger.ms、压缩算法、SSD 设备等而不是去折磨 fsync。对绝大多数 Kafka 集群来说多副本加acksall已经能提供足够强的持久性保障再额外把刷盘阈值调得很低是得不偿失的。3.4 一套可以落地的初始参数建议我知道读者都想要一个能直接抄作业的配置但必须承认没有绝对万能的值。结合实测我给出几个常见的组合参考在此基础上根据自己的磁盘和消息量微调。场景log.flush.interval.messageslog.flush.interval.ms说明生产多副本集群追求吞吐不配保持默认不配保持默认可靠性靠acksallmin.insync.replicas保证单副本重要数据 Topic100001000断电窗口压缩到秒级代价是吞吐下降约 30%开发测试环境5000500适合反复重启的快照场景极端安全场景10每条都刷盘性能极差基本不推荐生产使用修改方式就是编辑config/server.properties在文件末尾加上log.flush.interval.messages10000 log.flush.interval.ms1000然后滚动重启 Broker。如果只想针对某个 Topic 修改就走前面说的kafka-configs.sh方式。注意在重启前先kafka-configs.sh --describe确认当前生效值避免改完忘了。4. 高频问题与排查避坑这些坑我基本都踩过写配置一回事真正出问题时很多人还是容易绕进死胡同。我把遇到的典型误区和排查顺序整理在下面。4.1 三个最容易踩的误区误区一把 flush.messages 设成 1 就是最保险。这么说吧设置 1 只意味着“每条消息都强制触发 fsync 判断”但每次 fsync 都是一次磁盘同步操作吞吐量会掉到惨不忍睹。而且前面说过Kafka 跨机器可靠性靠副本机制单机刷得再勤也扛不住整个机架断电。所以“每条刷盘”不是保险更像是自残。误区二默认配置就是完全不落盘。默认不主动刷盘不代表不落盘。操作系统后台回写线程会把脏页批量写回磁盘只是时机不由 Kafka 决定。重启进程的时候未刷盘的数据一般还在页缓存里接管后照样能正常读取极端情况下数据丢失通常只发生在断电、内核崩溃磁盘子系统异常这些层面。所以“默认配置会立刻丢数据”这个说法不准确。误区三消息延迟高先怀疑刷盘。我在第 3 节里那个案例就是例子。Kafka 消息延迟高的原因太多了Producer 端 linger.ms 太小导致小请求过多、acksall 带来的副本同步耗时、消费者 rebalance、分区热点、磁盘 IO 被别的任务挤占等这些都排在刷盘参数前面。盲目调低 flush 参数不仅没解决延迟反而把磁盘拖垮。4.2 消息延迟高时建议按这个顺序排查我自己在线上排查延迟问题时遵循的是下面这个顺序看 Producer 指标batch 是否太小、linger.ms是否为 0、消息是否在本地积压。小请求是 Kafka 最常见的吞吐杀手。看 Broker 的 IO 状况用iostat -x 1看磁盘 util 和 await用vmstat 1看 CPU 的 wa 占比快速判断磁盘是不是瓶颈。看网络线程和请求队列kafka-run-class.sh kafka.tools.JmxTool或者监控面板上的RequestQueueAvgTimeMs、LocalTimeMs、RemoteTimeMs能区分是 Broker 内部处理慢还是跨机同步慢。再看刷盘配置确认有没有人把flush.messages或flush.ms调得很低。如果确实调低了先回滚再观察。看消费者 Lag如果消费端处理不过来Producer 端再快也没用。大多数时候刷盘配置不是延迟问题的第一嫌疑对象所以放到后置比较合理。如果一上来就改flush.messages反而可能把问题改得更大。4.3 怎么确认刷盘配置真的生效了配置改完后我建议做一次确认别等到故障了才发现没生效。最直接的方式是查看 Broker 配置kafka-configs.sh --bootstrap-server localhost:9092 \ --describe --entity-type brokers --entity-name 1输出里会包含log.flush.interval.messages和log.flush.interval.ms的当前值。如果是 Topic 级修改把--entity-type改成topics再指定--entity-name即可。另一个土办法是监听日志文件的修改时间。写入消息后查看日志目录ls -l --time-stylefull-iso /tmp/kafka-logs/test-topic-0/正常情况下.log文件的 mtime 变化比较“零散”因为操作系统是异步回写不会每次写入都改。如果配置了很低的刷盘阈值观察*.log文件的 mtime 会显得很规律且有明显的周期性更新这也能从侧面验证刷盘触发行为。用监控系统看磁盘方面也有帮助在刷盘生效期间iostat -x 1里的w_await和util会出现周期性尖峰sar -B 1可以看到页缓存回写相关指标。这些指标准确度没有 kafka-configs 那么直接但胜在能实时反映刷盘对磁盘负载的影响。4.4 配套工具用 Kafka UI 类和性能工具辅助排查很多人会搜 Kafka 可视化工具、Kafka 有没有 UI 界面之类的问题。Kafka 本身不带 Web 界面但如果需要观察 Topic 的flush.messages、flush.ms配置以及查看分区 Lag 和写入延迟Kafka UI、Offset Explore 这类工具就很好用。它们可以直接在界面上看到 Topic 的配置覆盖省的每次查配置都敲命令。性能方面压测时推荐用 Kafka 官方自带的性能工具kafka-producer-perf-test.sh --topic test-topic --num-records 100000 \ --record-size 500 --throughput 5000 --producer-props bootstrap.serverslocalhost:9092如果配置调整前后各跑一轮配合iostat观察磁盘负载很容易判断新配置对吞吐和延迟的影响。我实测下来这种“先压测再改参、再压测”的流程比凭空拍脑袋可靠得多。5. 最后再分享一点个人体会这些实验和排障做下来我最大的感受是Kafka 的刷盘配置不是“越高越好”也不是“越低越好”它本质上是在回答一个问题——如果这台机器突然断电你愿意为最近几秒的数据付出多大的性能代价。多副本机制能解决大部分可靠性问题刷盘参数只是单机层面的补充保险千万别把它当成灵丹妙药。如果踩过几次坑之后让我给一个建议那就是新集群先用默认配置跑靠副本数量撑可靠性如果某个单副本 Topic 数据确实敏感才去单独配置flush.messages和flush.ms并且务必用压测验证性能损失。配置生效后用kafka-configs.sh确认一遍再在灾备演练里强制断电一次实测一下到底丢不丢数据。数据安全这件事验证出来的结论比理论推演可靠得多。