kafka系列——KafkaProducer源碼分析

實例化過程

在KafkaProducer的構造方法中,根據配置項主要完成以下對象或數據結構的實例化

① 配置項中解析出 clientId,用於跟蹤程序運行情況,在有多個KafkProducer時,若沒有配置 client.id則clientId 以前 輟”producer-”後加一個從 1 遞增的整數

② 根據配置項創建和註冊用於Kafka metrics指標收集的相關對象,用於對 Kafka 集羣相關指標的追蹤

③ 實例化分區器,用於爲消息指定分區,客戶端可以通過實現Partitioner 接口自定義消息分配分區的規則,默認使用 DefaultPartitioner(該分區器分配分區的規則是:若消息指定了Key,則對Key取hash值,然後與可用的分區總數求模; 若沒有指定Key,則DefalutPartitioner通過一個隨機數與可用的總分區數取模)

④ 實例化消息Key和Value進行序列化操作的Serializer,默認ByteArraySerializer(可自定義,但消費端也要指定相應的 反序列化操作) ⑤ 根據配置實例化一組攔截器(ProducerInterceptor)

⑥ 實例化用於消息發送相關元數據信息的MetaData對象,MetaData由用於控制MetaData進行更新操作的相關配置信 息與集羣信息Cluster(保存集羣中所有主題與分區的各種信息)組成

⑦ 實例化用於存儲消息的RecordAccumulator,作用類似一個隊列(後面詳細介紹)

⑧ 根據指定的安全協議${security.protocol}創建一個 ChannelBuilder,然後創建NetworkClient實例,這個對象的底層是 通過維持一個Socket連接來進行TCP通信的,用於生產者與各個代理進行 Socket 通信

⑨ 由NetworkClient對象構造一個用於數據發送的Sender實例sender 線程,最後通過sender創建一個KafkaThread線 程,啓動該線程,該線程是一個守護線程,在後臺不斷輪詢,將消息發送給代理

消息發送過程介紹

首先,若客戶端定義了攔截器鏈,則消息會經過每個攔截器的onSend方法進行處理,而後纔會進入KafkaProducer的 doSend方法進行發送的處理,doSend方法處理流程如下:

① 阻塞式獲取MetaData,超過${max.block.ms}時間依舊未獲取到,則拋TimeoutException,消息發送失敗

② 對消息的key與value進行序列化

③ 根據消息計算其將要發往的分區,若客戶端發送消息時指定partitionId,則直接返回所指定的partitionId,否則根據分 區器定義的分區分配策略計算出 partitionId

④ 消息長度有效性檢查,超過${max.request.size}或${buffer.memory}所設閾值,都會拋RecordTooLargeException

⑤ 創建TopicPartition對象(記錄消息的topic與分區信息),在RecordAccumulator中會爲每個TopicPartiton對象創建一 個雙端隊列(後面介紹)

⑥ 構造Callback對象,該對象最終會交由ProducerBatch處理

⑦ 寫BufferPool操作,這一步是調用RecordAccumulator.append()方法將ProducerRecord寫入RecordAccumulator 的BufferPool中,並返回處理結果(後面介紹)

RecordAccumulator介紹

其作用相當於一個緩衝隊列,會根據主題和分區(TopicPartition對象)對消息進行分組,每一個TopicPartition對象會對應 一個雙端隊列Deque<ProducerBatch>,ProducerBatch表示一批消息,在KafkaProducer發送消息時,總是從隊列隊尾 (Tail)取出ProducerBatch(如果隊列不爲空),而Sender是從隊列頭(Head)取ProducerBatch進行處

RecordAccumulator追加消息append()流程

記錄當前正在進行append消息的線程數,方便當客戶端調用 KafkaProducer.close()強制關閉發送消息操作時放棄未處理完的請求,釋放資源

根據TopicPartition獲取or創建消息對應的雙端隊列 Deque<ProducerBatch>,並與其進行關聯

③嘗試將消息寫入其所屬的緩衝區

④ 消息成功寫入buffer後,會做如下處理

⑤ 若上述嘗試append消息失敗,即返回null,此時需要向BufferPool申請空間用於創建新的ProducerBatch對象,並將

消息append到新創建的ProducerBatch中,最後返回處理結果

Sender發送消息的基本流程

總體流程:從MetaData中獲取集羣信息→從RecordAccumulator中取出已滿足發送條件的ProducerBatch→構造相關 網絡層請求交由 NetworkClient 去執行

① 從MetaData中獲取集羣Cluster的信息

② 獲取各TopicPartition分區的 Leader 節點集合

③ 若第②步返回的result中的unknownLeaderTopics不爲空(即存在沒有找到Leader分區的主題),則遍歷 unknownLeaderTopics將主題信息加入metaData中,然後調用metaData.requestUpdate()方法將needUpdate設置爲 true,請求更新metaData 信息

④ 檢測result中readyNodes集合各節點連接狀態,若與某個節點的連接還未就緒則將該節點從readyNodes中移除,經過 NetworkClient.ready()方法處理之後,readyNodes集合中的所有節點均已與NetworkClient建立了連接

⑤ 根據readyNodes集合中的節點信息,到RecordAccumulator中取出每個topic分區對應的雙端隊列deque,並從每個 deque頭部開始取ProducerBatch作爲每個節點所要發送的消息集合

⑥ 根據配置項${request.timeout.ms}的值,默認是 30s,過濾掉請求已超時的ProducerBatch,若已超時則將該 ProducerBatch添加到過期隊列List中,並將該ProducerBatch從雙端隊列中移除,同時釋放內存空間;然後將過期的 ProducerBatch交由SenderMetrics進行處理,更新和記錄相應的metrics信息

⑦ 遍歷第⑤步得到的batches,根據batches分組的Node,將每 Node轉化爲一個ClientRequest對象,最終將 batches 轉化  List<ClientRequest>集合

⑧ 遍歷第⑦步得到的List<ClientRequest>集合,首先調用NetworkClient.send(ClientRequest request,long  now)方法執 行網絡層消息傳輸,向相應代理髮送請求

⑨ 在 send()方法中首先將ClinetRequest 添加到InFlightRequests 隊列中,該隊列記錄了一系列正在被髮送或是已發送 但還沒收到響應的ClientRequest,然後調用Selector.send(Send send)方法,但此時數據並沒有真的發送出去,只是暫存 在Selector內部相對應的KafkaChannel裏面(每個Node對應一個KafkaChannel,用來記錄每個Node對應的數據包,一個 KafkaChannel 一次只能存放一個數據包,在當前的數據包沒有完整發出去之前不能存放下一個數據包,否則拋異常)

⑩ 在Sender的run(long now)方法的結尾調用NetworkClient.poll(long  timeout,long now)方法真正進行讀寫操作,該方 法首先調用MetadataUpdater.maybeUpdate(long  now)方法檢查是否需要更新元數據信息,然後調用 Selector.poll(long  timeout)方法執行真正的 I/O 操作,最後對已經完成的請求對其響應結果response 進行處理

Sender線程run()方法的控制邏輯

① 首先會通過標誌位running來控制主循環,在running爲true時一直循環調用run(long now)方法,直到KafkaProducer 調用close()方法時將runing設置爲 false

② 若close操作是強制關閉,(如:在調用close()方法時設置timeout 爲0,或是不正確的close()方法調用,如直接調用 KafkaProducer 實例化時創建的用於I/O操作的KafkaThread線程的close方法等,則調用RecordAcccumulator 的 abortIncompleteBatches()方法,丟棄未處理的請求,將未處理的ProducerBatch從其雙端隊列中移除,同時關閉 RecordBatch 釋放空間;若是強制關閉,同時消息累加器尚有消息未發送(accumulator.hasUnsent())或者客戶端尚有正 在處理(inFlightRequestCount()>0)的請求,則繼續循環調用run(long  now)方法,將RecordAccumulor中存儲的未發送的 請求以及正在發送中的請求處理完畢;最後調用NetworkClinet.close()方法,關閉NetworkClinet持有的用於執行I/O 操作 的Selector及與其關聯的連接通道KafkaChannel

這部分完~

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章