Kafka Java API 之Producer原始碼解析

五柳-先生發表於2015-11-20

原帖地址: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的形式寫入,獲得最大效率

相關文章