【背景】 我负责一条日均 2 亿条的事件处理链路(Kafka 3.6 + Spring Kafka 3.1 + MySQL 8.0),消费者侧用手动 ack(AckMode.MANUAL_IMMEDIATE 和 MANUAL 都试过)。上线一年多,踩过两个坑:
- MANUAL_IMMEDIATE 逐条 ack:QPS 稳定 5k 没问题,但下游 DB 写偶尔抖到 80ms,一条慢就卡住整条 poll 循环,rebalance 概率飙升(max.poll.interval 被吃光)。
- 改成 MANUAL + 每 200 条 flush 一次:吞吐上去了,但一次 GC 停顿 300ms 期间已处理未 ack 的 180 条消息,rebalance 后全部重投,下游幂等表扛了 3 分钟重复写入。
两种方案各有代价,但团队里至今没有统一标准——不同业务组各配各的,运维巡检时同一集群里能看到 4 种 ack 策略。
【常见分歧 / 几种做法】 我调研了几家朋友团队的配置,大致分三派:
- 逐条 ack 派:「反正下游有幂等,少 ack 一条就少一次重投」,适合消息处理耗时方差小(<10ms)且吞吐不太高的场景。
- 批量 ack 派:「一次 poll 拉 500 条,处理完统一 commit 一次 offset,DB 少 500 次 fsync,rebalance 窗口可接受」,通常配合幂等表 + 去重窗口 10 分钟。
- 定时 flush 派:「不关心条数,每 2 秒或每 500ms 强制 commit 一次,处理快就少 commit,处理慢就多攒」,折中但调参依赖经验。
还有一个隐蔽的第四个选项:分区级 vs 消息级 ack(Spring Kafka 的 MANUAL 实际是分区级 offset commit),很多人以为自己在做消息级 ack,其实不是——中间任何一条处理失败,已 ack 的不会回滚。
【我的倾向与理由】 我目前的落地策略是混合粒度:
- 单条处理耗时 < 50ms 且下游幂等成本极低(Redis SETNX 去重)→ MANUAL_IMMEDIATE,简单粗暴;
- 单条处理耗时 > 200ms 或涉及多表事务 → 按 batch 处理完后 MANUAL commit,batch 大小 = max.poll.records / 4(留 75% 余量给 poll 循环本身);
- 无论哪种,max.poll.interval.ms 至少留 2 倍于「最慢 batch 处理时间」的安全余量,否则 rebalance 是定时炸弹。
核心理由:ack 粒度的本质是用重复投递的代价换 ack 写放大,这个 trade-off 取决于「重投一次的业务代价」和「多一次 offset commit 的 RTT」哪个更贵,而不是「哪种更安全」。
【想听大家怎么选】 想听各位聊聊:
- 你们线上 ack 策略是统一配置还是按业务组自选?有没有出过「同一消费者组里两种 ack 模式打架」的事故?
- 批量 ack 场景下,幂等去重窗口你们怎么定?10 分钟够不够?有没有人踩过窗口太短导致乱序重投的坑?
- Spring Kafka 的 MANUAL 模式底层是 commitSync 还是 commitAsync?你们有没有人手动切到 commitAsync 过,延迟和丢 offset 的风险怎么评估?
- 如果让你给一个新团队定「ack 策略决策树」,你会画几层判断条件?
欢迎贴你们的 application.yml 片段(脱敏即可),比文字描述直观。
0赞
0收藏
0楼层