Kafka Java API 之Producer源碼解析

本文系原創,轉載請註明!

原帖地址:http://blog.csdn.net/xeseo/article/details/18315451

從我的OneNote copy過來,格式似乎有點問題,懶得整了。將就着看吧,各位。

Kafka提供了Producer類作爲java producerapi該類有syncasync兩種發送方式。

 

默認是sync方式,即producer的調用類在消息真正發送到隊列中去以後才返回,其工作原理如下:

 

new Producer

 

當我們new了一個kafka java api提供的Producer類時,其底層,實際會產生兩個核心類的實例:Producer(Scala的,不是java api提供的那個)DefaultEventHandler。在創建的同時,會默認new一個ProducerPool,即我們每new一個javaProducer類,就會有一個scalaProducerEventHandlerProducerPool

 

producer.send()

當我們調用javaproducer.send方法時,底層調用scalaProducersend方法,其內部其實調的是eventhandler.handle(message)方法。

  1. eventHandler會首先序列化該消息,

             eventHandler.serialize(events

 

  1. 然後會根據傳入的broker信息、topic信息,去取最新的該topicmetadata信息

BrokerPartitionInfo.updateInfo

 | -> ClientUtils.fetchTopicMetadata   //創建一個topicMetadataRequest,並隨機的選取傳入的broker信息中任何一個去取metadata,直到取到爲止

         | -> val producer: SyncProducer = ProducerPool.createSyncProducer(producerConfig, shuffledBrokers(i)) //對隨機選到的broker會創建一個SyncProducer

                     | -> SyncProducer.send  //發送topicMetadataRequest到該broker去取metadata,獲得該topic所對應的所有的broker信息

 

看到這裏也就明白了爲什麼kafkaapi裏面並不要求你提供完整的整個kafka集羣的broker信息,而是任選一個或幾個。因爲在這裏它會去你提供的broker取該topic的最新的所有的broker信息。

這裏要注意的是,用於發送topicMetadataRequest的SyncProducer雖然是用ProducerPool.createSyncProducer方法建出來的,但用完並不還回pool,而是直接Close,所以會發現有INFO log打出來

[INFO] <main> Connected toxxx.xxx.xxx.xxx:9092 for producing

[INFO] <main> Disconnecting fromxxx.xxx.xxx.xxx:9092

注意:

這個刷新metadata並不僅在第一次初始化時做。爲了能適應kafka broker運行中因爲各種原因掛掉、paritition改變等變化,eventHandler會定期的再去刷新一次該metadata,刷新的間隔用參數topic.metadata.refresh.interval.ms定義,默認值是10分鐘。

這裏有三點需要強調:

  1. 不調用send, 不會建立socket,不會去定期刷新metadata
  2. 在每次取metadata時,kafka會單獨開一個socket去取metadata,開完再關掉。
  3. 根據取得的最新的完整的metadata,刷新Pool中到broker的連接(第一次建立時,pool裏面是空的)
  4. 每10分鐘的刷新會直接重新把到每個broker的socket連接重建,意味着在這之後的第一個請求會有幾百毫秒的延遲。如果不想要該延遲,把topic.metadata.refresh.interval.ms值改爲-1,這樣只有在發送失敗時,纔會重新刷新。Kafka的集羣中如果某個partition所在的broker掛了,可以檢查錯誤後重啓重新加入集羣,手動做rebalance,producer的連接會再次斷掉,直到rebalance完成,那麼刷新後取到的連接着中就會有這個新加入的broker。

在ClientUtils.fetchTopicMetadata調用完成後,回到BrokerPartitionInfo.updateInfo繼續執行,在其末尾,pool會根據上面取得的最新的metadata建立所有的SyncProducer,即Socket通道producerPool.updateProducer(topicsMetadata)

注意:

ProducerPool中,SyncProducer的數目是由該topicpartition數目控制的,即每一個SyncProducer對應一個broker,內部封了一個到該brokersocket連接。

每次刷新時,會把已存在SyncProducerclose掉,即關閉socket連接,然後新建SyncProducer,即新建socket連接,去覆蓋老的。

如果不存在,則直接創建新的。

 

  1. 然後,纔是真正發送數據

dispatchSerializedData(outstandingProduceRequests)

 

  1. 如果發送失敗,會進行重試。重試時,又會刷新metadata,而kafkaleader選舉需要一定的時間,所以這次刷新可能需要等待,最大等待時間由參數retry.backoff.ms(默認爲100)定義。

重試最大次數由參數message.send.max.retries定義默認爲3

 

 

async方式通過參數producer.type控制,例子:

Properties p = new Properties();

props.put("producer.type", "async");

ProducerConfig config = new ProducerConfig(props);

producer = new Producer<String, byte[]>(config);

 

async方式與sync方式的不同在於,在初始化scalaproducer時,會創建一個ProducerSendThread對象。然後,在調用send時,它並不是直接調用eventHandler.handle方法,而是把消息放入一個長度由queue.buffering.max.messages參數定義的隊列(默認10000),當隊列滿足以下兩種條件時,會由ProducerSendThread觸發eventHandler.handle方法,把隊列中的消息作爲一個batch發送

  1. 時間超過queue.buffering.max.ms定義的值,默認5000ms
  2. 隊列中當前消息個數超過batch.num.messages定義的值,默認200

 

結論:

1. Kafka提供的java api中的Producer,底層只是維護該topic到每個broker的連接,並不是一個傳統意義上的連接池。在使用sync方式時,我們應該自己實現一個連接池,裏面包含若干Producer對象,以實現最大化寫入效率。我自己寫了一個簡單的:https://github.com/EdisonXu/simple-kafka-producer-pool

2. 在寫入的數據頻率不高或要求獲得寫入結果時,應使用sync方式,否則會因async的等待時間引入額外的延遲

3. 在寫入的數據頻率很高時,應使用async方式,以batch的形式寫入,獲得最大效率

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