1

前言

这次是继 《记 Kafka Consumer 消息阻塞(1)》 之后,其实应该是放在同一篇文章里面。但因为是新问题,就再加一篇文章。

还是继那篇文章,提出要调大 max.partition.fetch.bytesmessage.max.bytes 的参数值。但是不能调太大,调太大之后,同样带来新的问题。

本次就是新问题。再调大10倍后,消费能力下降了不止100倍。

通过消费的监控图来看,不是不消费,而是隔将近半小时才消费一次。

原以为这两个参数只是名义上只限制上限,不会影响实际值,但大错特错。

1. 场景参数

下面我们模拟场景吧,设定的条件:

  • 每条消息大小:1 KB
  • max.poll.records = 100
    → 每次 poll() 返回给应用线程的记录数最多 100 条(即约 100 KB)。
  • max.partition.fetch.bytes = 50 MB
    → 单个分区一次 fetch 请求最多拉取 50 MB 数据。
  • fetch.max.bytes = 100 MB
    → 一次 fetch 请求总返回数据上限为 100 MB(所有分区合计)。

假设:

  • 消费者订阅了多个分区,比如 3 个分区。
  • 每个分区上有大量可消费数据(远超过 50 MB)。
  • 网络和内存都足够大,不会限制拉取。

2. Kafka 拉取数据的两个阶段

理解这个过程的关键是分清拉取阶段应用消费阶段

阶段 A:消费者从 broker 拉取到本地缓冲区

  • 消费者后台线程(Fetcher)会周期性向 broker 发送 FetchRequest
  • Broker 按你的参数限制:

    • 每个分区不超过 50 MB(max.partition.fetch.bytes)。
    • 所有分区总和不超过 100 MB(fetch.max.bytes)。
  • 在你的场景:

    • 如果订阅了 3 个分区,Broker 可能会返回:

      • P1: 50 MB
      • P2: 50 MB
      • P3: 不返回(因为总量已经到 100 MB)
    • 一次网络包大小 ≈ 100 MB(受 fetch.max.bytes 限制)。
  • 这些数据会被放到消费者端的内部缓冲区(fetch buffer),等待应用线程消费。
注意:这个阶段与 max.poll.records 无关,因为 max.poll.records 是应用线程从缓冲区取数据的限制,不影响后台拉取量。

阶段 B:应用线程从缓冲区取数据

  • 当你调用 poll() 方法时,Kafka 消费者会从缓冲区中取出消息。
  • max.poll.records = 100 意味着一次 poll() 最多返回 100 条记录(100 KB)。
  • 即使缓冲区中已经有 100 MB 数据,应用线程一次也只会拿 100 KB。
  • 剩下的数据会继续留在缓冲区,等下一次 poll() 再取。

3. 完整时序

我们按时间顺序看一次拉取和消费的过程:

  1. 后台线程发送 Fetch 请求

    • 请求分区 P1、P2、P3 的数据。
    • 告诉 broker:单分区最多 50 MB,总量最多 100 MB。
  2. Broker 返回数据

    • P1: 50 MB
    • P2: 50 MB
    • 总量达到 100 MB,P3 暂时不返回。
    • 数据通过网络传输到消费者端。
  3. 数据进入消费者缓冲区

    • 内部缓冲区现在有 100 MB 数据。
  4. 应用线程调用 poll()

    • 从缓冲区取出 100 条记录(每条 1 KB) → 共 100 KB。
    • 返回给你的业务代码处理。
  5. 缓冲区剩余数据

    • 还剩下 100 MB - 100 KB ≈ 99.9 MB 数据在缓冲区中。
    • 下次 poll() 会继续从剩余数据中取,不会再立即拉取新的数据(除非缓冲区不足)。
  6. 循环进行

    • 当缓冲区数据消耗到一定程度,后台线程会再次向 broker 拉取数据,填充到缓冲区。

4. 数据量总结

在你的场景中:

阶段数据量控制参数
一次从 broker 拉取到缓冲区≤ 100 MB(总量受 fetch.max.bytes 限制,单分区 ≤ 50 MB)fetch.max.bytesmax.partition.fetch.bytes
一次 poll() 返回给应用线程≤ 100 KB(100 条 × 1 KB)max.poll.records

关键点

  • 拉取量消费量是两个不同的概念。
  • 拉取量受 fetch.max.bytesmax.partition.fetch.bytes 控制。
  • 消费量受 max.poll.records 控制。
  • 如果拉取量远大于消费量,缓冲区可能长期积压数据,占用大量内存。

5. 额外注意

  • 如果你的 max.poll.records 很小,而拉取量很大,缓冲区会一直积压数据,可能导致延迟消费。
  • 如果消费者的处理速度慢,fetch.max.bytes 设置太大,可能会导致内存压力。
  • Kafka 内部还有一个参数 max.poll.interval.ms,如果 poll 间隔太久,消费者会被认为挂掉,触发 rebalance。
可视化流程图(简化版)
[Broker]
   ↑ Fetch Response (<= fetch.max.bytes)
   |   ├─ P1: <= max.partition.fetch.bytes
   |   ├─ P2: <= max.partition.fetch.bytes
   |   └─ ...
[Consumer 内部缓冲区]
   ↓ poll() (<= max.poll.records 条)
[应用线程处理]

KerryWu
679 声望171 粉丝

保持饥饿