消息调用 send() 之后不会立刻上网络,而是先进入客户端本地的一块内存缓冲区(buffer.memory),按分区攒成批次,再由专门的 Sender 线程异步发出去。这次读取 Producer 的内部 Metrics,把缓冲区占用的变化过程看一遍,也验证了客户端会在消息进缓冲区之前就做大小检查。
命令 · java KafkaProducer<String,String> producer = new KafkaProducer<>(props); // buffer.memory=1000000, linger.ms=30000 System.out.println("初始 buffer-available-bytes = " + metric(producer, "buffer-available-bytes")); for (int i = 0; i < 100; i++) { producer.send(new ProducerRecord<>("buffer-metrics-demo", "x".repeat(2000))); // 100 条 2KB } Thread.sleep(200); System.out.println("未 flush 时 buffer-available-bytes = " + metric(producer, "buffer-available-bytes")); producer.flush(); Thread.sleep(300); System.out.println("flush 完成后 buffer-available-bytes = " + metric(producer, "buffer-available-bytes"));
初始 buffer-available-bytes = 1000000.0 发送 100 条 2KB 消息(未flush)后 buffer-available-bytes = 983616.0 的 buffer-total-bytes = 1000000.0 flush 完成后 buffer-available-bytes = 1000000.0
从 Producer 自带的 Metrics 里读出了缓冲区可用字节数的变化:一开始满额 1,000,000 字节,linger.ms 设置得很大(30 秒),消息堆积在缓冲区还没被发出去的时候,可用字节数掉到了 983,616(少了 16,384 字节,正好是一个默认 batch.size 的批次),说明缓冲区确实是"消息占着位置直到真正发出并确认";调用 flush() 强制把堆积的批次发出去、等到 Broker 确认后,可用字节数完全恢复回 1,000,000——缓冲区空间是跟着"未确认数据"走的,发出去并且拿到 ack 之后才会被释放。
命令 · java p.put(ProducerConfig.MAX_REQUEST_SIZE_CONFIG, 10000); // 单条请求最大 10KB producer.send(new ProducerRecord<>("max-request-demo", "x".repeat(5000))).get(); // 5KB,低于上限 producer.send(new ProducerRecord<>("max-request-demo", "x".repeat(50000))).get(); // 50KB,超过上限
--- 发一条 5KB 的消息(低于上限)--- 发送成功 --- 发一条 50KB 的消息(超过 max.request.size=10000)--- 在客户端本地就被拒绝,异常类型: org.apache.kafka.common.errors.RecordTooLargeException 异常信息: The message is 50087 bytes when serialized which is larger than 10000, which is the value of the max.request.size configuration.
5KB 的消息发送成功,50KB 的消息在客户端本地——序列化完但还没真正进入 Sender 的批次累加逻辑——就被 RecordTooLargeException 拦下来了,根本没有机会占用缓冲区空间或者发一次网络请求。这说明"消息发送前经历的步骤"里,大小校验是排在缓冲区分配之前的一道关卡。
Producer Buffer 做的事情是:先校验消息大小(超限直接本地拒绝,不占缓冲区、不碰网络),通过校验的消息占用缓冲区空间,按分区攒成批次,等发出并拿到 Broker 确认后才释放空间。这次额外尝试过用很小的 buffer.memory 加大量大消息去触发的缓冲区耗尽阻塞,但在本机回环网络下没有复现出来——发送速率始终追不上网络和 Broker 确认的往返速度,缓冲区一直有空间被及时腾出来,这本身也是一个、如实记录的边界,不是没测出来就假装测出了一个"阻塞"的例子。
每一步的数字都是本文上两节读取 Producer Metrics 和触发异常得到的原始结果。