面到頭冷之Kafka拾遺(一)- 生產者

概括

總的來講,一個kafka生產者要發送一條消息,要經過以下幾個步驟:
1.創建生產者實例。
2.組裝消息對象ProducerRecord。
3.經過攔截器,進行一些定制化處理。
4.經過序列化器,把對象轉化成字節數組。
5.在分區器中確定要發送的分區,每個分區都有一個隊列,隊列中每一個元素都是一批消息,這樣做的好處是為了批量發送,減少請求次數,降低網絡IO。
6.根據發送方式(異步or同步),對發送成功或失敗的請求進行回調處理。
7.關閉生產者實例。


image.png

準備階段

消息對象ProducerRecord
public class ProducerRecord<K, V> { 
    private final String topic; //主題
    private final Integer partition; //分區號 
    private final Headers headers; //消息頭
    private final K key; //鍵
    private final V value; //值
    private final Long timestamp; // 時間戳 
}

topic就是發送到主題,partition就是該主題的分區,key的作用也是來確定分區的。但是partition和key之間有如下約定:

  1. partition優先級比key高,當partition沒有值時,才會用 hash(key)去確定這條消息的分區。
  2. 如果partition和key都沒指定,則會按照輪詢的方式發送到每個該topic下的每個分區。
生產者攔截器

攔截器是在消息發送前進行一些預處理,對消息做一些定制化需求,一個攔截器需要繼承org.apache.kafka.clients.producer.ProducerInterceptor接口,其中包含3個方法:

public ProducerRecord onSend(ProducerRecord record){}
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {}
public void close(){}

onSend方法會在消息序列化之前被調用,可以通過這個方法對消息分區和消息內容等進行修改。onAcknowledgement方法會在消息被應答之前或消息發送失敗的時候調用,先于回調函數執行前執行,盡量不要在這個方法內執行繁重的邏輯,影響發送的io效率。剩下的close方法就是用來關閉一些資源。

序列化

生產者需要把對象序列化成字節數組才能傳輸到kafka,消費者則需要反序列化將字節數組轉化為本地對象。生產者和消費者之間需要滿足一定的序列化協議,保證生產者發送的消息是消費者可以解析的格式。要構造一個序列化器,需要實現org.apache.kafka.common.serialization.Serializer接口,此接口包含3個方法:

public void configure(Map<String, ?> configs, boolean isKey){}
public byte[] serialize(String topic, String data){}
public void close(){}

其中configure方法是拿到外部配置的參數,之后會在serialize方法中按需使用這些參數。serialize方法就是具體的序列化方法,可以在這里面定制協議,按照一定的順序格式返回一個字節數組。close方法用來關閉序列化器,一般這個方法都是個空方法。

分區器

分區器主要用來確定消息要發送到哪個分區,默認的分區器主要是由ProducerRecord類中的partition和key確定兩個字段進行判斷,partition和key之間關系已經在上面說過了,就不多贅述。

發送階段

消息收集器-RecordAccumulator

在準備階段完成之后,所要發送的topic和分區已經確定,之后消息并不是直接發送給kafka,而是要進入一個名為RecordAccumulator的消息收集器,這個收集器是起到一個緩沖的作用,對于每一個分區,他都維護了一個雙端隊列,每次發送給kafka都會以隊列中的一個或多個元素為單位進行發送,這個元素名叫ProducerBatch,ProducerBatch實際上就是多個ProducerRecord組成的集合,對多條消息進行批量發送,這樣可以大幅減少網絡IO次數,提升整體的吞吐量。


image.png
請求隊列-InFlightRequest

最終,一組ProducerBatch會組裝成一個Request發往各個Broker,在發送前會將Request存放到一個名叫InFlightRequest的Map中,這個Map格式為Map<NodeId,Deque<Request>>,用來保存發送到每個Broker但還未接收到響應的請求,當隊列中的元素越來越多時,說明這個broker節點負載比較大,繼續發送請求可能會請求超時,默認每個NodeId下的Deque大小為5,超過該數值后則暫停向這個節點發送消息,可以通過max.in.flight.requests.per.connection來修改。
同時,從InFlightRequest我們還能獲取kafka的節點、副本等一些元數據,提供給Request去建立連接。


image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容