送消息的 Broker)
為什么 ready 了還不發(fā)上一篇講了 batch 什么時候變 READY。但很多人遇到過這種情況監(jiān)控顯示 batch 已經(jīng) ready 了消息卻沒發(fā)出去延遲持續(xù)走高。原因很簡單accumulator.ready()返回的readyNodes只是這個 Broker 上有 ready 的 batch不代表這個 Broker 現(xiàn)在可以接收消息。Sender 拿到 readyNodes 后還要逐個節(jié)點調(diào)用client.ready(node)做三層過濾通過了才會真正 drain 發(fā)送。01 三層過濾全景從 readyNodes 到可發(fā)送節(jié)點要經(jīng)過三層過濾第一層連接狀態(tài)過濾。調(diào)用NetworkClient.isReady(node)檢查 TCP 連接是否已建立、SASL 認證是否完成、握手是否完成。只有連接狀態(tài)為 READY 才通過。未連接的節(jié)點會調(diào)用initiateConnect()發(fā)起連接連接中的節(jié)點本輪跳過。第二層in-flight 過濾。調(diào)用inFlightRequests.canSendMore(node.id)檢查該節(jié)點當前未確認的請求數(shù)是否小于max.in.flight.requests.per.connection默認 5。滿了說明 Broker 處理不過來繼續(xù)發(fā)只會導致請求排隊超時。第三層元數(shù)據(jù)更新過濾。檢查metadataUpdater.isUpdateDue(nowMs)如果元數(shù)據(jù)過期需要刷新本輪優(yōu)先發(fā) MetadataRequest暫停普通消息發(fā)送——避免用過期元數(shù)據(jù)把消息發(fā)到錯誤的 Broker。三層都通過的節(jié)點才加入可發(fā)送節(jié)點集傳給accumulator.drain()拉取批次。02 NetworkClient.ready() 的判斷邏輯client.ready(node, nowMs)內(nèi)部是一個與邏輯三個條件同時滿足才返回 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元數(shù)據(jù)未在更新if(metadataUpdater.isUpdateDue(nowMs))returnfalse;returntrue;}連接狀態(tài)機有 4 個狀態(tài)NOT_CONNECTED → CONNECTING → AUTHENTICATING → READY。只有 READY 狀態(tài)isReady()才返回 true。首次發(fā)送時要經(jīng)過 TCP 三次握手 SASL 認證 TLS 握手如果啟用這就是為什么 Producer 啟動后第一條消息延遲特別高。in-flight 計數(shù)是每個 Broker 連接獨立維護的。發(fā)一個 ProduceRequest 計數(shù) 1收到 ACK 計數(shù) -1。默認上限 5調(diào)大能提高吞吐但可能導致 Broker 壓力過大調(diào)小如 1能保證嚴格順序但犧牲吞吐。冪等生產(chǎn)者enable.idempotencetrue會自動限制為 ≤5。03 Sender.runOnce 中的完整流程把過濾邏輯放回 Sender 的主循環(huán)完整流程是這樣的voidrunOnce(longnowMs,longmetadataTimeoutMs){Clusterclustermetadata.fetch();// 1. 獲取有 ready batch 的節(jié)點RecordAccumulator.ReadyCheckResultresultaccumulator.ready(cluster,nowMs);// 2. 逐個節(jié)點過濾未通過的從集合中移除for(Nodenode:result.readyNodes){if(!client.ready(node,nowMs))result.readyNodes.remove(node);}// 3. 用過濾后的節(jié)點拉取批次MapInteger,ListProducerBatchbatchesaccumulator.drain(cluster,result.readyNodes,maxRequestSize,nowMs);// 4. 構(gòu)建請求并發(fā)送sendProduceRequests(batches,nowMs);}注意一個細節(jié)過濾是直接修改 readyNodes 集合remove 未通過的節(jié)點不是創(chuàng)建新集合。所以傳給 drain 的 readyNodes 已經(jīng)是過濾后的結(jié)果。04 為什么 ready 了但沒發(fā)出去4 種場景場景1連接未建立。首次發(fā)送或連接斷開重連時TCP 握手 認證需要時間。監(jiān)控connection-count和connection-creation-total如果連接數(shù)持續(xù)增長說明在頻繁重連。解決Producer 啟動后預熱發(fā)一條空消息觸發(fā)連接建立或者接受首次延遲。場景2in-flight 已滿。這是最常見的吞吐瓶頸。監(jiān)控in-flight-requests-count如果持續(xù)接近max.in.flight默認5說明 Broker 處理不過來。這不是 Producer 的問題是 Broker 端瓶頸——排查磁盤 IO、CPU、網(wǎng)絡。調(diào)大max.in.flight可能暫時緩解但根本解決要優(yōu)化 Broker。場景3元數(shù)據(jù)更新中。metadata.max.age.ms到期默認5分鐘或 Broker 變化時觸發(fā)元數(shù)據(jù)刷新期間普通消息暫停。表現(xiàn)為間歇性延遲尖刺持續(xù)幾百 ms。這是正常保護機制更新完自動恢復。如果頻繁更新排查 Broker 穩(wěn)定性是否有頻繁的 Leader 切換。場景4Broker 不可用 / Leader 切換。Broker 宕機觸發(fā) Leader 重選舉舊 Leader 不再是 Leader發(fā)送會報NOT_LEADER_OR_FOLLOWER。元數(shù)據(jù)刷新后重新路由到新 Leader重試發(fā)送。retries足夠大時自動恢復超過delivery.timeout.ms則報錯。寫在最后理解這三層過濾后“batch ready 了但沒發(fā)出去就不再是玄學。核心思路ready 只是可以發(fā)”真正發(fā)送還要過連接、in-flight、元數(shù)據(jù)三道關(guān)。排查時按順序檢查三個監(jiān)控指標connection-count連接是否正常、in-flight-requests-count是否接近上限、metadata-age是否在更新。99% 的問題都能在這三個指標里定位到。