bigywater.tech
常见报错 · 49

Kafka CommitFailedException:真正的原因是两次 poll 间隔超过 max.poll.interval.ms,超时后重新 poll 从 offset 0 重复消费,新协议下 commitSync 还会“成功”却没有提交

CommitFailedException 说“消费者被踢出了组”,但原因几乎总是“处理一批消息花的时间超过了 max.poll.interval.ms”。这篇用 Kafka 4.3.1 在本机复现它:看日志和异常的原文、重复消费是怎么发生的、三种修法的区别,并发现新消费组协议下同样的超时不抛异常。

0
超时后重新 poll 的第一条 offset:这 5 条被重复消费;最后查询已提交 offset 是 null
2067 ms
新协议下日志里 poll 间隔超出 max.poll.interval.ms 的毫秒数,commitSync 却返回成功
4 / 30
max.poll.records=2 的修法后,服务端 CURRENT-OFFSET 与 LOG-END-OFFSET,LAG 为 26

复现

实测 · 本机执行输出
  classic_fail(max.poll.records=5,max.poll.interval.ms=3000,每条处理 1000 ms,group.protocol=classic):
      [ 5973 ms] 第 1 次 poll 拿到 5 条,第一条 offset = 0
      LOG WARN ConsumerCoordinator: [Consumer clientId=consumer-g_classic_fail-1, groupId=g_classic_fail] consumer poll timeout has expired. This means the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time processing messages. You can address this either by increasing max.poll.interval.ms or by reducing the maximum size of batches returned in poll() with max.poll.records.
      [11001 ms] 处理完 5 条(每条 1000 ms),准备 commitSync
      [11002 ms] commitSync 失败:org.apache.kafka.clients.consumer.CommitFailedException
          message: Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group.
      [11013 ms] 第 2 次 poll 拿到 5 条,第一条 offset = 0
      LOG WARN ConsumerCoordinator: [Consumer clientId=consumer-g_classic_fail-1, groupId=g_classic_fail] consumer poll timeout has expired. This means the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time processing messages. You can address this either by increasing max.poll.interval.ms or by reducing the maximum size of batches returned in poll() with max.poll.records.
      [16048 ms] 处理完 5 条(每条 1000 ms),准备 commitSync
      [16048 ms] commitSync 失败:org.apache.kafka.clients.consumer.CommitFailedException
          message: Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group.
      最后查询已提交 offset:[null]

两次 poll 的间隔超过 max.poll.interval.ms,commitSync 抛 CommitFailedException,重新 poll 重复消费。

三种修法

实测 · 本机执行输出
  fix_records(max.poll.records=2,max.poll.interval.ms=3000,每条处理 1000 ms,group.protocol=classic):
      [ 5462 ms] 第 1 次 poll 拿到 2 条,第一条 offset = 0
      [ 7477 ms] 处理完 2 条(每条 1000 ms),准备 commitSync
      [ 7487 ms] commitSync 成功
      [ 7488 ms] 第 2 次 poll 拿到 2 条,第一条 offset = 2
      [ 9506 ms] 处理完 2 条(每条 1000 ms),准备 commitSync
      [ 9507 ms] commitSync 成功
      最后查询已提交 offset:[OffsetAndMetadata{offset=4, leaderEpoch=0, metadata=''}]
  fix_interval(max.poll.records=5,max.poll.interval.ms=10000,每条处理 1000 ms,group.protocol=classic):
      [ 5484 ms] 第 1 次 poll 拿到 5 条,第一条 offset = 0
      [10517 ms] 处理完 5 条(每条 1000 ms),准备 commitSync
      [10520 ms] commitSync 成功
      [10520 ms] 第 2 次 poll 拿到 5 条,第一条 offset = 5
      [15546 ms] 处理完 5 条(每条 1000 ms),准备 commitSync
      [15547 ms] commitSync 成功
      最后查询已提交 offset:[OffsetAndMetadata{offset=10, leaderEpoch=0, metadata=''}]
  fix_speed(max.poll.records=5,max.poll.interval.ms=3000,每条处理 300 ms,group.protocol=classic):
      [ 5458 ms] 第 1 次 poll 拿到 5 条,第一条 offset = 0
      [ 6973 ms] 处理完 5 条(每条 300 ms),准备 commitSync
      [ 6984 ms] commitSync 成功
      [ 6985 ms] 第 2 次 poll 拿到 5 条,第一条 offset = 5
      [ 8519 ms] 处理完 5 条(每条 300 ms),准备 commitSync
      [ 8521 ms] commitSync 成功
      最后查询已提交 offset:[OffsetAndMetadata{offset=10, leaderEpoch=0, metadata=''}]

减小 max.poll.records、调大 max.poll.interval.ms、加快处理,本质是让一批的处理时间小于间隔。

新协议

实测 · 本机执行输出
  new_fail(max.poll.records=5,max.poll.interval.ms=3000,每条处理 1000 ms,group.protocol=consumer):
      [ 5553 ms] 第 1 次 poll 拿到 5 条,第一条 offset = 0
      LOG WARN ConsumerHeartbeatRequestManager: [Consumer clientId=consumer-g_new_fail-1, groupId=g_new_fail] Consumer poll timeout has expired. This means the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time processing messages. You can address this either by increasing max.poll.interval.ms or by reducing the maximum size of batches returned in poll() with max.poll.records.
      [10593 ms] 处理完 5 条(每条 1000 ms),准备 commitSync
      [10594 ms] commitSync 成功
      LOG WARN ConsumerHeartbeatRequestManager: [Consumer clientId=consumer-g_new_fail-1, groupId=g_new_fail] Time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, exceeded approximately by 2067 ms. Member vNfjoi4BSZGdzBgnrK5TKg will rejoin the group now.
      [10820 ms] 第 2 次 poll 拿到 5 条,第一条 offset = 0
      LOG WARN ConsumerHeartbeatRequestManager: [Consumer clientId=consumer-g_new_fail-1, groupId=g_new_fail] Consumer poll timeout has expired. This means the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time processing messages. You can address this either by increasing max.poll.interval.ms or by reducing the maximum size of batches returned in poll() with max.poll.records.
      [15855 ms] 处理完 5 条(每条 1000 ms),准备 commitSync
      [15856 ms] commitSync 成功
      最后查询已提交 offset:[]

group.protocol=consumer 时 commitSync 返回成功但没有提交,不抛异常。

服务端核对

实测 · 本机执行输出
  消费组 g_classic_fail:
      (没有已提交的 offset)
  消费组 g_new_fail:
      (没有已提交的 offset)
  消费组 g_fix_records:
      GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
      g_fix_records orders 0 4 30 26 - - -

用 kafka-consumer-groups --describe 看提交点,不要只信客户端。

分步演示

提交失败
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Homebrew Kafka 4.3.1(KRaft 单节点,127.0.0.1:9580)、kafka-clients 4.3.1、OpenJDK 21.0.13;topic orders 1 个分区、30 条消息;客户端日志只保留 WARN 以上的 consumer 内部日志;时间点包含 JVM 启动与加入消费组的时间,所以第一次 poll 在 5~6 秒
    • 没有验证:多个消费者(真正发生 rebalance、分区被别的成员接走)时的行为;自动提交(enable.auto.commit=true)下的表现;librdkafka / confluent-kafka-python 里对应的错误码(_MAX_POLL_EXCEEDED);Spring Kafka 的 ackMode 与异常处理;session.timeout.ms 与心跳线程超时(这里只测了 poll 间隔);新协议下 commitSync 返回成功但没有提交的原因(只是现象);Kafka 其它版本
    京ICP备2026067502号-1京公网安备11010502063487号