
为什么 ready 了还不发上一篇讲了 batch 什么时候变 READY。但很多人遇到过这种情况监控显示 batch 已经 ready 了消息却没发出去延迟持续走高。原因很简单accumulator.ready()返回的readyNodes只是这个 Broker 上有 ready 的 batch不代表这个 Broker 现在可以接收消息。Sender 拿到 readyNodes 后还要逐个节点调用client.ready(node)做三层过滤通过了才会真正 drain 发送。01 三层过滤全景从 readyNodes 到可发送节点要经过三层过滤第一层连接状态过滤。调用NetworkClient.isReady(node)检查 TCP 连接是否已建立、SASL 认证是否完成、握手是否完成。只有连接状态为 READY 才通过。未连接的节点会调用initiateConnect()发起连接连接中的节点本轮跳过。第二层in-flight 过滤。调用inFlightRequests.canSendMore(node.id)检查该节点当前未确认的请求数是否小于max.in.flight.requests.per.connection默认 5。满了说明 Broker 处理不过来继续发只会导致请求排队超时。第三层元数据更新过滤。检查metadataUpdater.isUpdateDue(nowMs)如果元数据过期需要刷新本轮优先发 MetadataRequest暂停普通消息发送——避免用过期元数据把消息发到错误的 Broker。三层都通过的节点才加入可发送节点集传给accumulator.drain()拉取批次。02 NetworkClient.ready() 的判断逻辑client.ready(node, nowMs)内部是一个与逻辑三个条件同时满足才返回 truepublicbooleanready(Nodenode,longnowMs){if(node.isEmpty())returnfalse;// 条件1连接就绪if(!connectionStates.isReady(node.id())){if(connectionStates.canConnect(node.id(),nowMs))initiateConnect(node,nowMs);returnfalse;}// 条件2in-flight 未满if(!inFlightRequests.canSendMore(node.id()))returnfalse;// 条件3元数据未在更新if(metadataUpdater.isUpdateDue(nowMs))returnfalse;returntrue;}连接状态机有 4 个状态NOT_CONNECTED → CONNECTING → AUTHENTICATING → READY。只有 READY 状态isReady()才返回 true。首次发送时要经过 TCP 三次握手 SASL 认证 TLS 握手如果启用这就是为什么 Producer 启动后第一条消息延迟特别高。in-flight 计数是每个 Broker 连接独立维护的。发一个 ProduceRequest 计数 1收到 ACK 计数 -1。默认上限 5调大能提高吞吐但可能导致 Broker 压力过大调小如 1能保证严格顺序但牺牲吞吐。幂等生产者enable.idempotencetrue会自动限制为 ≤5。03 Sender.runOnce 中的完整流程把过滤逻辑放回 Sender 的主循环完整流程是这样的voidrunOnce(longnowMs,longmetadataTimeoutMs){Clusterclustermetadata.fetch();// 1. 获取有 ready batch 的节点RecordAccumulator.ReadyCheckResultresultaccumulator.ready(cluster,nowMs);// 2. 逐个节点过滤未通过的从集合中移除for(Nodenode:result.readyNodes){if(!client.ready(node,nowMs))result.readyNodes.remove(node);}// 3. 用过滤后的节点拉取批次MapInteger,ListProducerBatchbatchesaccumulator.drain(cluster,result.readyNodes,maxRequestSize,nowMs);// 4. 构建请求并发送sendProduceRequests(batches,nowMs);}注意一个细节过滤是直接修改 readyNodes 集合remove 未通过的节点不是创建新集合。所以传给 drain 的 readyNodes 已经是过滤后的结果。04 为什么 ready 了但没发出去4 种场景场景1连接未建立。首次发送或连接断开重连时TCP 握手 认证需要时间。监控connection-count和connection-creation-total如果连接数持续增长说明在频繁重连。解决Producer 启动后预热发一条空消息触发连接建立或者接受首次延迟。场景2in-flight 已满。这是最常见的吞吐瓶颈。监控in-flight-requests-count如果持续接近max.in.flight默认5说明 Broker 处理不过来。这不是 Producer 的问题是 Broker 端瓶颈——排查磁盘 IO、CPU、网络。调大max.in.flight可能暂时缓解但根本解决要优化 Broker。场景3元数据更新中。metadata.max.age.ms到期默认5分钟或 Broker 变化时触发元数据刷新期间普通消息暂停。表现为间歇性延迟尖刺持续几百 ms。这是正常保护机制更新完自动恢复。如果频繁更新排查 Broker 稳定性是否有频繁的 Leader 切换。场景4Broker 不可用 / Leader 切换。Broker 宕机触发 Leader 重选举旧 Leader 不再是 Leader发送会报NOT_LEADER_OR_FOLLOWER。元数据刷新后重新路由到新 Leader重试发送。retries足够大时自动恢复超过delivery.timeout.ms则报错。写在最后理解这三层过滤后“batch ready 了但没发出去就不再是玄学。核心思路ready 只是可以发”真正发送还要过连接、in-flight、元数据三道关。排查时按顺序检查三个监控指标connection-count连接是否正常、in-flight-requests-count是否接近上限、metadata-age是否在更新。99% 的问题都能在这三个指标里定位到。