1. 问题描述
最近在接入一个新的埋点 Kafka Topic 后,遇到一个非常隐蔽的问题:
- 新 Topic 一直消费不到消息;
- 在公司 MQ 平台上查看 Topic,查不到消费组的注册信息;
- 日志平台没有任何 Error 日志。
消费端的逻辑是典型的主动拉取模式,由异步线程循环执行:
poll()拉取一批消息;- 事务处理(读取消息、执行脚本、写入存储);
commit提交偏移量。
初步怀疑过:
- Consumer 没注册成功? 检查 Bean 加载正常;
- 新 Kafka 集群地址网络不通? Ping 正常;
- 事务处理异常? 加注释排查,不是。
最终通过代码跟踪定位到 org.apache.kafka.clients.consumer.KafkaConsumer#poll,第一步就抛了异常,但由于是 UncheckedException,没有被业务代码 catch 打印,所以表面上看不到错误。
异常内容:
org.apache.kafka.common.errors.RecordTooLargeException:
There are some messages at [Partition=Offset]: {new_bury_point_full_data_topic-3=1912557955}
whose size is larger than the fetch size 1048576 and hence cannot be ever returned.
Increase the fetch size, or decrease the maximum message size the broker will allow.问题原因:拉取的消息内容太大,超过了 1048576(1MB)的上限。
搜索配置发现,这个值并没有显式设置,是 Kafka Consumer 端 max.partition.fetch.bytes 的默认值。
这类问题的隐蔽性在于:
- Kafka 在 Producer、Broker、Consumer 三端都有消息大小限制;
- RocketMQ 只有 Broker 端有限制;
- 在 Kafka 消费端,如果批次中有一条消息超过限制,整个拉取会失败并卡在当前 offset,后续消息无法消费;
- 长期无法提交 offset,MQ 平台上就看不到消费组“在线”。
接下来,我们深入讲解 Kafka 与 RocketMQ 在消息大小限制上的机制。
2. Kafka Consumer 参数
2.1. 拉取数量
- 作用:
控制poll()方法每次调用 从 Kafka 返回的最大消息条数。 - 单位:条(record 条数)
- 默认值:
500 影响:
- 限制了单次批量处理的消息数量,防止一次拉取太多数据导致内存压力过大或处理延迟过长。
- 如果值太大,可能导致处理时间长,从而触发
max.poll.interval.ms超时,导致消费者被认为失效。 - 如果值太小,会增加拉取次数,降低吞吐量。
调优建议:
- 根据单条消息大小、处理速度和业务延迟要求来设置。
- 如果处理速度较快,可以适当调大,提升吞吐量。
- 如果单条消息很大或者处理很耗时,建议调小,避免一次 poll 拉取过多数据。
2.2. 单分区拉取上限
- 作用:
控制 每个分区 一次 fetch 请求中返回的最大字节数。 - 单位:字节(bytes)
- 默认值:
1MB(1048576 字节) 影响:
- 这是按分区的限制。例如一个消费者订阅了 3 个分区,那么一次 fetch 请求理论上最多可以返回
3 × max.partition.fetch.bytes的数据。 - 如果消息很大(单条消息接近或超过该值),需要保证
max.partition.fetch.bytes大于等于message.max.bytes(Broker 端)或max.message.bytes(Topic 级别),否则消费者无法拉取该消息。
- 这是按分区的限制。例如一个消费者订阅了 3 个分区,那么一次 fetch 请求理论上最多可以返回
调优建议:
- 如果单条消息比较大,需要调大该值。
- 如果分区数多,调大该值会显著增加一次 fetch 的数据量,需要考虑内存压力。
2.3. 拉取总上限
- 作用:
控制 一次 fetch 请求 从所有分区总共返回的最大字节数。 - 单位:字节(bytes)
- 默认值:
50MB(52428800 字节) 影响:
- 这是一个总量限制,即使
max.partition.fetch.bytes允许每个分区返回更多数据,但总和不能超过fetch.max.bytes。 - 如果分区很多,并且每个分区都接近
max.partition.fetch.bytes,可能会因为fetch.max.bytes限制而无法一次拉完所有数据。
- 这是一个总量限制,即使
调优建议:
- 如果消费者订阅的分区较多,并且每个分区消息量大,可以适当调大该值。
- 但调太大可能会造成网络和内存压力。
2.4. 三者关系总结
max.poll.records:限制的是 条数(处理批次大小)。max.partition.fetch.bytes:限制的是 单分区单次请求的最大字节数。fetch.max.bytes:限制的是 单次请求总字节数(所有分区加起来)。
Kafka 拉取数据的流程中,这几个参数是层层限制的:
一次 poll() 能取到的数据量
= min( max.poll.records 条数限制,
数据总字节数不超过 fetch.max.bytes,
每个分区的数据字节数不超过 max.partition.fetch.bytes )2.5. 调优建议示例
假设:
- 消费者订阅 5 个分区
- 单条消息平均大小:10KB
- 处理速度:500 条/秒
- 希望单次 poll 处理 1 秒的数据量
调优:
max.poll.records = 500(一次 poll 拉 500 条)单个分区一次拉取的最大数据量:
max.partition.fetch.bytes >= 10KB × (500 / 5分区) = 1MB总拉取量:
fetch.max.bytes >= 5 × max.partition.fetch.bytes
3. Kafka 各端限制
好的,这个问题其实很关键,因为 Kafka 在 Producer → Broker → Consumer 整个链路上都有消息大小的限制,而且这几个限制相互关联,如果配置不一致会导致生产或消费失败。我们来详细拆解一下。
Kafka 一条消息从 Producer 发出,到 Broker 存储,再到 Consumer 拉取,中间会经过三个端的限制:
- Producer 端限制
- Broker 端限制
- Consumer 端限制
每一端都有自己的参数控制最大消息大小,必须保证三端的限制一致或相互匹配,否则会出错。
3.1. Producer 端限制
核心参数
max.request.size- 作用:Producer 端单次请求(包含一批消息)的最大字节数。
- 默认值:
1MB(1048576 字节) 注意:
- 这是单次发送的总大小限制,不是单条消息的限制。如果是批量发送(batch),所有消息加起来不能超过这个值。
- 如果单条消息就超过该值,会直接报错(
RecordTooLargeException)。
buffer.memory- 作用:Producer 端用于缓冲待发送消息的总内存大小。
- 默认值:
32MB - 影响:如果发送速度大于网络传输速度,缓冲区满了会阻塞
send()或抛异常。
batch.size- 作用:Producer 端按分区批量发送的最大字节数。
- 默认值:
16KB - 影响:不是直接的消息大小限制,但会影响批量发送策略。
✅ 调优建议:
- 如果要发送大消息(比如几 MB),必须同时调大
max.request.size,并且保证 Broker 端允许的大小更大。 - 如果批量消息总大小超过
max.request.size会报错,需要调大。
3.2. Broker 端限制
核心参数
message.max.bytes- 作用:Broker 端单条消息允许的最大字节数。
- 默认值:
1MB 注意:
- 如果 Producer 发送的消息超过这个值,Broker 会拒绝并返回错误。
- 这是 Broker 层面全局的限制,可以在
server.properties配置。
replica.fetch.max.bytes- 作用:Follower 副本从 Leader 拉取消息时,单次请求的最大字节数。
- 默认值:
1MB 注意:
- 必须 ≥ message.max.bytes,否则副本无法同步大消息,会导致 ISR 缩小甚至丢数据。
Topic 级别的限制
- 可以在创建 Topic 时用
max.message.bytes单独设置该 Topic 的最大消息大小,优先级高于全局message.max.bytes。
- 可以在创建 Topic 时用
✅ 调优建议:
如果要支持大消息,必须保证:
replica.fetch.max.bytes >= message.max.bytes- 如果有多个 Topic,可以按需调整 Topic 级别的
max.message.bytes。
3.3. Consumer 端限制
核心参数
max.partition.fetch.bytes- 作用:Consumer 从单个分区一次 fetch 请求的最大字节数。
- 默认值:
1MB 注意:
- 必须 ≥ message.max.bytes,否则 Consumer 无法拉取该分区的大消息。
- 如果一个分区有一条大消息超过这个值,消费会卡住(因为无法拉取该消息)。
fetch.max.bytes- 作用:Consumer 一次 fetch 请求从所有分区总共能拉取的最大字节数。
- 默认值:
50MB 注意:
- 必须保证该值 ≥ 订阅分区数 ×
max.partition.fetch.bytes,否则可能一次拉不全数据。
- 必须保证该值 ≥ 订阅分区数 ×
调优建议:
- 如果 Broker 存了大消息,Consumer 必须调大
max.partition.fetch.bytes。 - 如果分区多且每个分区消息都很大,
fetch.max.bytes也要跟着调大。
3.4. 三端参数对应关系
假设要发送最大 10MB 的单条消息:
Producer:
max.request.size >= 10MB
buffer.memory >= 10MB(否则可能阻塞)
Broker:
message.max.bytes >= 10MB
replica.fetch.max.bytes >= 10MB
Consumer:
max.partition.fetch.bytes >= 10MB
fetch.max.bytes >= 分区数 × 10MB链路要求:
Producer.max.request.size ≤ Broker.message.max.bytes ≤ Consumer.max.partition.fetch.bytes并且:
Broker.replica.fetch.max.bytes ≥ Broker.message.max.bytes总结表
| 端 | 参数 | 默认值 | 作用 | 调优重点 |
|---|---|---|---|---|
| Producer | max.request.size | 1MB | 单次请求最大字节数 | ≥ 消息大小 |
buffer.memory | 32MB | 缓冲区大小 | ≥ 批量消息总大小 | |
| Broker | message.max.bytes | 1MB | 单条消息最大字节数 | ≥ Producer 最大消息 |
replica.fetch.max.bytes | 1MB | 副本拉取最大字节数 | ≥ message.max.bytes | |
| Consumer | max.partition.fetch.bytes | 1MB | 单分区拉取最大字节数 | ≥ message.max.bytes |
fetch.max.bytes | 50MB | 总拉取最大字节数 | ≥ 分区数 × 单分区限制 |
3.5. 常见错误场景
Producer 发大消息失败
- 原因:
max.request.size小于消息大小。 - 解决:调大 Producer 的
max.request.size。
- 原因:
Broker 拒收消息
- 原因:
message.max.bytes小于 Producer 消息大小。 - 解决:调大 Broker 的
message.max.bytes,同时调大replica.fetch.max.bytes。
- 原因:
Consumer 卡住不消费
- 原因:
max.partition.fetch.bytes小于 Broker 存储的大消息。 - 解决:调大 Consumer 的
max.partition.fetch.bytes和fetch.max.bytes。
- 原因:
4. RocketMQ 各端
好的,我们来讲一下 RocketMQ 在 Producer → Broker → Consumer 三端的消息大小限制,以及它和 Kafka 的区别。
RocketMQ 的消息大小限制机制比 Kafka 简单一些,但也有几个关键点需要注意,尤其是在大消息场景下。
RocketMQ 链路回顾
RocketMQ 消息流转过程:
Producer → Broker(CommitLog 存储) → Consumer在这条链路上,每一端都有自己的限制参数或机制来控制单条消息大小。
4.1. Producer 端限制
核心参数
maxMessageSize- 作用:Producer 允许发送的单条消息的最大字节数。
- 默认值:
4MB(4194304 字节) - 位置:
org.apache.rocketmq.client.producer.DefaultMQProducer 注意:
- 这是单条消息(
Message对象)的限制,不是批量消息总大小。 - 如果消息超过这个大小,Producer 会直接抛出
MESSAGE_SIZE_EXCEEDED异常。 - 如果是批量消息(
send(List<Message>)),会按总大小来检查。
- 这是单条消息(
批量消息发送限制
- RocketMQ 会在发送批量消息时检查所有消息总大小不能超过
maxMessageSize。 - 这和 Kafka 的
max.request.size类似。
- RocketMQ 会在发送批量消息时检查所有消息总大小不能超过
✅ 调优建议:
- 如果需要发送大消息,必须在 Producer 端调大
maxMessageSize,同时 Broker 端也要允许更大的消息。 - RocketMQ 官方不建议发送特别大的消息(>4MB),推荐用 分片 + 合并 或者 外部存储 + 存储引用。
4.2. Broker 端限制
核心参数
maxMessageSize- 作用:Broker 允许存储的单条消息的最大字节数。
- 默认值:
4MB - 位置:
broker.conf 注意:
- 必须 ≥ Producer.maxMessageSize,否则 Broker 会拒收超大消息。
- Broker 存储层(CommitLog)会按照这个限制来写入数据。
网络传输限制
- RocketMQ 使用 Netty 传输消息,在 Broker 和 Producer/Consumer 之间传输的消息大小受
maxMessageSize控制。 - 如果超过这个值,网络层也会拒绝。
- RocketMQ 使用 Netty 传输消息,在 Broker 和 Producer/Consumer 之间传输的消息大小受
✅ 调优建议:
- 如果要支持大消息,Broker 的
maxMessageSize必须调大,并且和 Producer 保持一致。 - 调大消息大小会增加磁盘 IO 压力和网络延迟,要评估性能影响。
4.3. Consumer 端限制
RocketMQ 的 Consumer 端没有像 Kafka 那样的复杂字节数限制参数,它主要依赖 Broker 的限制来保证消息大小不会超过 Consumer 能处理的范围。但也有一些隐性限制:
拉取消息的批量大小
- 参数:
pullBatchSize - 默认值:
32(条数) - 作用:一次拉取的最大消息条数。
- 注意:如果单条消息很大,批量拉取多条可能导致内存压力。
- 参数:
一次拉取的最大字节数
- 参数:
pullThresholdSizeForQueue - 默认值:
100MB - 作用:针对单个队列一次拉取的总字节数限制(RocketMQ 4.9+)。
- 注意:这个值必须 ≥ 单条最大消息大小,否则会卡住。
- 参数:
✅ 调优建议:
- 如果 Broker 允许大消息,Consumer 端要保证
pullThresholdSizeForQueue足够大。 - 批量拉取时要结合单条消息大小调整
pullBatchSize,防止一次拉取太多占用过多内存。
4.4. 常见错误场景
Producer 发送大消息失败
- 原因:Producer
maxMessageSize小于消息大小。 - 解决:调大 Producer 的
maxMessageSize。
- 原因:Producer
Broker 拒收消息
- 原因:Broker
maxMessageSize小于 Producer 消息大小。 - 解决:调大 Broker 的
maxMessageSize。
- 原因:Broker
Consumer 内存溢出
- 原因:一次拉取太多大消息,
pullBatchSize和pullThresholdSizeForQueue没有控制好。 - 解决:调小批量条数,或调大总字节数限制。
- 原因:一次拉取太多大消息,
4.5. RocketMQ VS Kafka 限制
| 对比点 | Kafka | RocketMQ |
|---|---|---|
| Producer 限制 | max.request.size(总请求字节数) | maxMessageSize(单条消息字节数) |
| Broker 限制 | message.max.bytes(单条消息字节数) | maxMessageSize(单条消息字节数) |
| Consumer 限制 | max.partition.fetch.bytes / fetch.max.bytes | pullThresholdSizeForQueue(总字节数) |
| 默认大小 | 1MB | 4MB |
| 配置复杂度 | 多参数,Producer/Broker/Consumer 端都需匹配 | 参数少,但 Producer/Broker 必须一致 |
| 大消息处理建议 | 分片、压缩、外部存储 | 分片、压缩、外部存储 |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。