1

1. 问题描述

最近在接入一个新的埋点 Kafka Topic 后,遇到一个非常隐蔽的问题:

  • 新 Topic 一直消费不到消息;
  • 在公司 MQ 平台上查看 Topic,查不到消费组的注册信息;
  • 日志平台没有任何 Error 日志。

消费端的逻辑是典型的主动拉取模式,由异步线程循环执行:

  1. poll() 拉取一批消息;
  2. 事务处理(读取消息、执行脚本、写入存储);
  3. 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 级别),否则消费者无法拉取该消息。
  • 调优建议

    • 如果单条消息比较大,需要调大该值。
    • 如果分区数多,调大该值会显著增加一次 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 拉取,中间会经过三个端的限制:

  1. Producer 端限制
  2. Broker 端限制
  3. Consumer 端限制

每一端都有自己的参数控制最大消息大小,必须保证三端的限制一致或相互匹配,否则会出错。

3.1. Producer 端限制

核心参数
  1. max.request.size

    • 作用:Producer 端单次请求(包含一批消息)的最大字节数。
    • 默认值1MB(1048576 字节)
    • 注意

      • 这是单次发送的总大小限制,不是单条消息的限制。如果是批量发送(batch),所有消息加起来不能超过这个值。
      • 如果单条消息就超过该值,会直接报错(RecordTooLargeException)。
  2. buffer.memory

    • 作用:Producer 端用于缓冲待发送消息的总内存大小。
    • 默认值32MB
    • 影响:如果发送速度大于网络传输速度,缓冲区满了会阻塞 send() 或抛异常。
  3. batch.size

    • 作用:Producer 端按分区批量发送的最大字节数。
    • 默认值16KB
    • 影响:不是直接的消息大小限制,但会影响批量发送策略。

调优建议

  • 如果要发送大消息(比如几 MB),必须同时调大 max.request.size,并且保证 Broker 端允许的大小更大。
  • 如果批量消息总大小超过 max.request.size 会报错,需要调大。

3.2. Broker 端限制

核心参数
  1. message.max.bytes

    • 作用:Broker 端单条消息允许的最大字节数。
    • 默认值1MB
    • 注意

      • 如果 Producer 发送的消息超过这个值,Broker 会拒绝并返回错误。
      • 这是 Broker 层面全局的限制,可以在 server.properties 配置。
  2. replica.fetch.max.bytes

    • 作用:Follower 副本从 Leader 拉取消息时,单次请求的最大字节数。
    • 默认值1MB
    • 注意

      • 必须 ≥ message.max.bytes,否则副本无法同步大消息,会导致 ISR 缩小甚至丢数据。
  3. Topic 级别的限制

    • 可以在创建 Topic 时用 max.message.bytes 单独设置该 Topic 的最大消息大小,优先级高于全局 message.max.bytes

调优建议

  • 如果要支持大消息,必须保证:

    replica.fetch.max.bytes >= message.max.bytes
  • 如果有多个 Topic,可以按需调整 Topic 级别的 max.message.bytes

3.3. Consumer 端限制

核心参数
  1. max.partition.fetch.bytes

    • 作用:Consumer 从单个分区一次 fetch 请求的最大字节数。
    • 默认值1MB
    • 注意

      • 必须 ≥ message.max.bytes,否则 Consumer 无法拉取该分区的大消息。
      • 如果一个分区有一条大消息超过这个值,消费会卡住(因为无法拉取该消息)。
  2. 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
总结表
参数默认值作用调优重点
Producermax.request.size1MB单次请求最大字节数≥ 消息大小
buffer.memory32MB缓冲区大小≥ 批量消息总大小
Brokermessage.max.bytes1MB单条消息最大字节数≥ Producer 最大消息
replica.fetch.max.bytes1MB副本拉取最大字节数≥ message.max.bytes
Consumermax.partition.fetch.bytes1MB单分区拉取最大字节数≥ message.max.bytes
fetch.max.bytes50MB总拉取最大字节数≥ 分区数 × 单分区限制

3.5. 常见错误场景

  1. Producer 发大消息失败

    • 原因:max.request.size 小于消息大小。
    • 解决:调大 Producer 的 max.request.size
  2. Broker 拒收消息

    • 原因:message.max.bytes 小于 Producer 消息大小。
    • 解决:调大 Broker 的 message.max.bytes,同时调大 replica.fetch.max.bytes
  3. Consumer 卡住不消费

    • 原因:max.partition.fetch.bytes 小于 Broker 存储的大消息。
    • 解决:调大 Consumer 的 max.partition.fetch.bytesfetch.max.bytes

4. RocketMQ 各端

好的,我们来讲一下 RocketMQProducer → Broker → Consumer 三端的消息大小限制,以及它和 Kafka 的区别。

RocketMQ 的消息大小限制机制比 Kafka 简单一些,但也有几个关键点需要注意,尤其是在大消息场景下。

RocketMQ 链路回顾

RocketMQ 消息流转过程:

Producer → Broker(CommitLog 存储) → Consumer

在这条链路上,每一端都有自己的限制参数或机制来控制单条消息大小。

4.1. Producer 端限制

核心参数
  1. maxMessageSize

    • 作用:Producer 允许发送的单条消息的最大字节数。
    • 默认值4MB(4194304 字节)
    • 位置org.apache.rocketmq.client.producer.DefaultMQProducer
    • 注意

      • 这是单条消息(Message 对象)的限制,不是批量消息总大小。
      • 如果消息超过这个大小,Producer 会直接抛出 MESSAGE_SIZE_EXCEEDED 异常。
      • 如果是批量消息(send(List<Message>)),会按总大小来检查。
  2. 批量消息发送限制

    • RocketMQ 会在发送批量消息时检查所有消息总大小不能超过 maxMessageSize
    • 这和 Kafka 的 max.request.size 类似。

调优建议

  • 如果需要发送大消息,必须在 Producer 端调大 maxMessageSize,同时 Broker 端也要允许更大的消息。
  • RocketMQ 官方不建议发送特别大的消息(>4MB),推荐用 分片 + 合并 或者 外部存储 + 存储引用

4.2. Broker 端限制

核心参数
  1. maxMessageSize

    • 作用:Broker 允许存储的单条消息的最大字节数。
    • 默认值4MB
    • 位置broker.conf
    • 注意

      • 必须 ≥ Producer.maxMessageSize,否则 Broker 会拒收超大消息。
      • Broker 存储层(CommitLog)会按照这个限制来写入数据。
  2. 网络传输限制

    • RocketMQ 使用 Netty 传输消息,在 Broker 和 Producer/Consumer 之间传输的消息大小受 maxMessageSize 控制。
    • 如果超过这个值,网络层也会拒绝。

调优建议

  • 如果要支持大消息,Broker 的 maxMessageSize 必须调大,并且和 Producer 保持一致。
  • 调大消息大小会增加磁盘 IO 压力和网络延迟,要评估性能影响。

4.3. Consumer 端限制

RocketMQ 的 Consumer 端没有像 Kafka 那样的复杂字节数限制参数,它主要依赖 Broker 的限制来保证消息大小不会超过 Consumer 能处理的范围。但也有一些隐性限制:

  1. 拉取消息的批量大小

    • 参数pullBatchSize
    • 默认值32(条数)
    • 作用:一次拉取的最大消息条数。
    • 注意:如果单条消息很大,批量拉取多条可能导致内存压力。
  2. 一次拉取的最大字节数

    • 参数pullThresholdSizeForQueue
    • 默认值100MB
    • 作用:针对单个队列一次拉取的总字节数限制(RocketMQ 4.9+)。
    • 注意:这个值必须 ≥ 单条最大消息大小,否则会卡住。

调优建议

  • 如果 Broker 允许大消息,Consumer 端要保证 pullThresholdSizeForQueue 足够大。
  • 批量拉取时要结合单条消息大小调整 pullBatchSize,防止一次拉取太多占用过多内存。

4.4. 常见错误场景

  1. Producer 发送大消息失败

    • 原因:Producer maxMessageSize 小于消息大小。
    • 解决:调大 Producer 的 maxMessageSize
  2. Broker 拒收消息

    • 原因:Broker maxMessageSize 小于 Producer 消息大小。
    • 解决:调大 Broker 的 maxMessageSize
  3. Consumer 内存溢出

    • 原因:一次拉取太多大消息,pullBatchSizepullThresholdSizeForQueue 没有控制好。
    • 解决:调小批量条数,或调大总字节数限制。

4.5. RocketMQ VS Kafka 限制

对比点KafkaRocketMQ
Producer 限制max.request.size(总请求字节数)maxMessageSize(单条消息字节数)
Broker 限制message.max.bytes(单条消息字节数)maxMessageSize(单条消息字节数)
Consumer 限制max.partition.fetch.bytes / fetch.max.bytespullThresholdSizeForQueue(总字节数)
默认大小1MB4MB
配置复杂度多参数,Producer/Broker/Consumer 端都需匹配参数少,但 Producer/Broker 必须一致
大消息处理建议分片、压缩、外部存储分片、压缩、外部存储

KerryWu
676 声望171 粉丝

保持饥饿