前言
这次是继 《记 Kafka Consumer 消息阻塞(1)》 之后,其实应该是放在同一篇文章里面。但因为是新问题,就再加一篇文章。
还是继那篇文章,提出要调大 max.partition.fetch.bytes、message.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)。
- 每个分区不超过 50 MB(
在你的场景:
如果订阅了 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. 完整时序
我们按时间顺序看一次拉取和消费的过程:
后台线程发送 Fetch 请求
- 请求分区 P1、P2、P3 的数据。
- 告诉 broker:单分区最多 50 MB,总量最多 100 MB。
Broker 返回数据
- P1: 50 MB
- P2: 50 MB
- 总量达到 100 MB,P3 暂时不返回。
- 数据通过网络传输到消费者端。
数据进入消费者缓冲区
- 内部缓冲区现在有 100 MB 数据。
应用线程调用 poll()
- 从缓冲区取出 100 条记录(每条 1 KB) → 共 100 KB。
- 返回给你的业务代码处理。
缓冲区剩余数据
- 还剩下 100 MB - 100 KB ≈ 99.9 MB 数据在缓冲区中。
- 下次
poll()会继续从剩余数据中取,不会再立即拉取新的数据(除非缓冲区不足)。
循环进行
- 当缓冲区数据消耗到一定程度,后台线程会再次向 broker 拉取数据,填充到缓冲区。
4. 数据量总结
在你的场景中:
| 阶段 | 数据量 | 控制参数 |
|---|---|---|
| 一次从 broker 拉取到缓冲区 | ≤ 100 MB(总量受 fetch.max.bytes 限制,单分区 ≤ 50 MB) | fetch.max.bytes、max.partition.fetch.bytes |
| 一次 poll() 返回给应用线程 | ≤ 100 KB(100 条 × 1 KB) | max.poll.records |
关键点:
- 拉取量和消费量是两个不同的概念。
- 拉取量受
fetch.max.bytes和max.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 条)
[应用线程处理]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。