背景
我带团队跑一条履约事件流:Kafka 3.5(KRaft,3 broker),topic fulfill-event 16 分区,消费组 8 实例(K8s Deployment)。消费端为了吞吐做了攒批:max.poll.records=500 → 逐条处理(写 MySQL 8.0 业务表 + 调下游 RPC)→ 整批 commitSync。
现象(卡点)
- 批内部分失败时,整批不提交 → 重启后 500 条全部重放,其中已成功的几百条全靠幂等表吸收,高峰期幂等表唯一键冲突 QPS 能到 3000+,MySQL 压力明显;
- 想改成「每处理完一条就推进提交点」,但 Kafka offset 是分区级的单调指针,单条提交 = 每 poll 周期最多 500 次 commitSync,broker 端 offset 提交风暴 + 客户端阻塞,实测 P99 直接翻倍;
- 也想过「每 50 条提交一次」,但批尾失败时最多重放 49 条,这个粒度怎么定全凭手感。
已尝试
- 幂等表唯一索引
uk_msg_key + 冲突即跳过:可行但冲突量不可控;
- 失败条进内存重试队列:进程崩溃时队列里的条目既没提交也没落库,状态不可观测;
- 逐条 commitAsync:重启窗口内「已 ack 未落库」的条目变成静默丢失,我们这条链路不能丢。
期望得到的答案形态
- 一个可复用的「批内部分失败」提交模型:明确每条消息的处理状态机(pending / done / dead)怎么落库、和 offset 提交点的推进关系;
- 提交粒度(每 N 条)的量化依据:怎么从「重放代价 = N × 单条处理成本」和「commit 频率 × 单次延迟」两边算出 N 的合理区间,最好带公式或数据;
- 如果你们用的是 outbox / 独立状态表记录「处理到第几条」,贴一下表结构和恢复逻辑。
相关参考(站内已读):
- 《Kafka 消费端批内部分失败:299 条成功 1 条失败,四种策略的代价量化与选型》(id=1892)
- 《Kafka 攒批落库:offset 提交与批量 flush 谁先谁后》(id=603)
- 《offset 提交粒度决定重复消费窗口的上下界》(id=1934)