【筆記】Kafka生產者理解

目錄

 

生產邏輯

參數說明

發送消息模式

代碼實現

序列化器

分區器

生產者客戶端的內部原理

參考資料


生產邏輯

1)配置生產者客戶端參數及創建相應的生產者實例;

2)構建待發送的消息;

3)發送消息;

4)關閉生產者實例。

參數說明

1)bootstrap.servers:指定連接Kafka 集羣所需的broker 地址清單,具體的內容格式爲hostl:portl,host2:port2 ,可以設置一個或多個地址,中間以逗號隔開,此參數的默認值爲“” 。注意這裏並非需要所有的broker 地址,因爲生產者會從給定的broker 裏查找到其他broker 的信息。不過建議至少要設置兩個以上的broker 地址信息,當其中任意一個巖機時,生產者仍然可以連接到Kafka集羣上。

2)key.serializer:消息中key 對應的序列化類,需要實現org.apache.kafka.common.serialization.Serializer接口,此參數的默認值爲“”。

3)value.serializer:消息中value 對應的序列化類,需要實現org.apache.kafka.common.serialization.Serializer接口,此參數的默認值爲“”。

4)client.id:這個參數用來設定KafkaProducer 對應的客戶端id ,默認值爲“” 。如果客戶端不設置, 則KafkaProducer 會自動生成一個非空字符串,內容形式如“ producer-1”“producer-2”。

5)buffer.memory:生產者客戶端中用於緩存消息的緩衝區大小,默認值爲33554432(32MB)。

6)batch.size:用於指定ProducerBatch 可以複用內存區域的大小,默認值爲16384(16KB)。

7)max.in.flight.requests.per.connection:限制每個連接(也就是客戶端與Node之間的連接)最多緩存的請求數,默認值爲5。

8)acks:指定分區中必須要有多少個副本收到這條消息,之後生產者纔會認爲這條消息是成功寫入的。涉及消息的可靠性和吞吐量之間的權衡。默認值爲"1",注意此值類型爲string。                                                                                                                  acks=1。生產者發送消息之後,只要分區的leader 副本成功寫入消息,那麼它就會收到來自服務端的成功響應。如果消息無法寫入leader 副本,比如在leader副本崩潰、重新選舉新的leader 副本的過程中,那麼生產者就會收到一個錯誤的響應,爲了避免消息丟失,生產者可以選擇重發消息。如果消息寫入leader 副本並返回成功響應給生產者,且在被其他follower 副本拉取之前leader 副本崩潰,那麼此時消息還是會丟失,因爲新選舉的leader副本中並沒有這條對應的消息。acks設置爲1,是消息可靠性和吞吐量之間的折中方案;
acks=0。生產者發送消息之後不需要等待任何服務端的響應。如果在消息從發送到寫入Kafka 的過程中出現某些異常,導致Kafka 並沒有收到這條消息,那麼生產者也無從得知,消息也就丟失了。在其他配置環境相同的情況下,acks設置爲0可以達到最大的吞吐量;
acks=-1或acks=all。生產者在消息發送之後,需要等待ISR中的所有副本都成功寫入消息之後才能夠收到來自服務端的成功響應。在其他配置環境相同的情況下,acks設置爲-1 (all)可以達到最強的可靠性。但這並不意味着消息就一定可靠,因
爲ISR中可能只有leader副本,這樣就退化成了acks=1 的情況。

9)max.request.size:這個參數用來限制生產者客戶端能發送的消息的最大值,默認值爲1048576B ,即1MB 。一般情況下,這個默認值就可以滿足大多數的應用場景了。不建議盲目地增大這個參數的配置值,尤其是在對Kafka 整體脈絡沒有足夠把控的時候。因爲這個參數還涉及一些其他參數的聯動。

10)retries:retries參數用來配置生產者重試的次數,默認值爲0,即在發生異常的時候不進行任何重試動作。消息在從生產者發出到成功寫入服務器之前可能發生一些臨時性的異常, 比如網絡抖動、leader副本的選舉等,這種異常往往是可以自行恢復的,生產者可以通過配置retries大於0 的值,以此通過內部重試來恢復而不是一昧地將異常拋給生產者的應用程序。如果重試
達到設定的次數,那麼生產者就會放棄重試並返回異常。

11)retry.backoff.ms:默認值爲100 , 它用來設定兩次重試之間的時間間隔,避免無效的頻繁重試。

注意:Kafka 可以保證同一個分區中的消息是有序的。如果生產者按照一定的順序發送消息,那麼這些消息也會順序地寫入分區,進而消費者也可以按照同樣的順序消費它們。對於某些應用來說,順序性非常重要,如果出現錯誤就會造成非常嚴重的後果。如果將acks參數配置爲非零值,並且max.in.flight.requests.per.connection參數配置爲大於1的值,那麼就會出現錯序的現象: 如果第一批次消息寫入失敗, 而第二批次消息寫入成功,那麼生產者會重試發送第一批次的消息, 此時如果第一批次的消息寫入成功,那麼這兩個批次的消息就出現了錯序。一般而言,在需要保證消息順序的場合建議把參數
max.in.flight.requests.per.connection 配置爲1 ,而不是把acks 配置爲0 ,不過這樣也會影響整體的吞吐。

發送消息模式

KafkaProducer是線程安全的,可以在多個線程中共享單個KafkaProducer 實例,也可以將KafkaProducer實例進行池化來供其他線程調用。
構建消息,即創建ProducerRecord對象。在實際的應用中,還會用到其他構造方法,比如要指定key ,或者添加headers等。有可能會遇到這些構造方法都不滿足需求的情況,需要自行添加更多的構造方法。注意,針對不同的消息,需要構建不同的ProducerRecord 對象,在實際應用中創建ProducerRecord 對象是一個非常頻繁的動作。
發送消息主要有三種模式:發後即忘(fire-and-forget)、同步(sync)及異步(async)。

發後即忘,它只管往Kafka 中發送消息而並不關心消息是否正確到達。在大多數情況下,這種發送方式沒有什麼問題, 不過在某些時候( 比如發生不可重試異常時〉會造成消息的丟失。這種發送方式的性能最高,可靠性也最差。

同步發送的方式可靠性高,要麼消息被髮送成功,要麼發生異常。如果發生異常,則可以捕獲並進行相應的處理,而不會像“發後即忘”的方式直接造成消息的丟失。不過同步發送的方式的性能會差很多,需要阻塞等待一條消息發送完之後才能發送下一條。

異步發送,使用Callback 的方式非常簡潔明瞭,Kafka有響應時就會回調,要麼發送成功,要麼拋出異常。onCompletion()方法的兩個參數是互斥的,消息發送成功時, metadata 不爲null 而exception 爲null:消息發送異常時,metadata 爲null 而exception 不爲null 。

close()方法會阻塞等待之前所有的發送請求完成後再關閉KafkaProducer。如果調用了帶超時時間timeout的close()方法,那麼只會在等待timeout時間內來完成所有尚未完成的請求處理, 然後強行退出。在實際應用中,一般使用的都是無參的close()方法。

代碼實現

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class KafkaProducerTest {
    public static final String brokerList = "host1:9092,host2:9092";
    public static final String topic = "test";

    public static Properties initConfig(){
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,brokerList);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        props.put(ProducerConfig.CLIENT_ID_CONFIG,"clientDemo");
        props.put(ProducerConfig.RETRIES_CONFIG,10);
        return props;
    }

    public static void main(String[] args){
        Properties props= initConfig();
        KafkaProducer<String,String> producer = new KafkaProducer<>(props);
        ProducerRecord<String,String> record = new ProducerRecord<>(topic,"hello,test");//此處可以有多種實現,比如指定headers,partition,key等
        //發後即忘:
        producer.send(record);
//        //同步:
//        try{
//            Future<RecordMetadata> future =  producer.send(record);
//            RecordMetadata metadata = future.get();
//            System.out.println(metadata.topic() +"----" +
//            metadata.partition() + ":"+ metadata.offset());
//        }catch(InterruptedException|ExecutionException e){
//            e.printStackTrace();
//        }
//        //異步:
//        producer.send(record, new Callback() {
//            public void onCompletion(RecordMetadata metadata, Exception exception) {
//                if (exception != null){
//                    exception.printStackTrace();
//                }else{
//                    System.out.println(metadata.topic() +"----" +
//                            metadata.partition() + ":"+ metadata.offset());
//                }
//            }
//        });

        producer.close();
    }

}

序列化器

生產者需要用序列化器(Serializer)把對象轉換成字節數組才能通過網絡發送給Kafka 。而在對側,消費者需要用反序列化器(Deserializer)把從Kafka 中收到的字節數組轉換成相應的對象。
生產者使用的序列化器和消費者使用的反序列化器是需要一一對應的,如果生產者使用了某種序列化器,比如StringSerializer,而消費者使用了另一種序列化器,比如IntegerSerializer ,那麼是無法解析出想要的數據的。

分區器

消息在通過send()方法發往broker的過程中,有可能需要經過攔截器( Interceptor )、序列化器(Serializer)和分區器(Partitioner)的一系列作用之後才能被真正地發往broker 。攔截器一般不是必需的,而序列化器是必需的。消息經過序列化之後就需要確定它發往的分區,如果消息ProducerRecord中指定了partition字段,那麼就不需要分區器的作用,因爲partition 代表的就是所要發往的分區號;如果消息ProducerRecord 中沒有指定partition 字段,那麼就需要依賴分區器, 根據key這個字段來計算partition 的值。分區器的作用就是爲消息分配分區。Kafka中提供的默認分區器是org.apache.kafka.clients.producer.intemals.DefaultPartitioner。

在默認分區器DefaultPartitioner的實現中,close()是空方法,而在partition()方法中定義了主要的分區分配邏輯。如果key不爲null,那麼默認的分區器會對key進行哈希(採用MurmurHash2算法,具備高運算性能及低碰撞率),最終根據得到的哈希值來計算分區號,擁有相同key的消息會被寫入同一個分區。如果key爲null ,那麼消息將會以輪詢的方式發往主題內的各個可用分區。

生產者客戶端的內部原理

整個生產者客戶端由兩個線程協調運行,這兩個線程分別爲主線程和Sender線程(發送線程)。在主線程中由KafkaProducer創建消息,然後通過可能的攔截器、序列化器和分區器的作用之後緩存到消息累加器(RecordAccumulator,也稱爲消息收集器〉中。Sender線程負責從RecordAccumulator中獲取消息並將其發送到Kafka 中。

參考資料

《深入理解Kafka:核心設計與實踐原理》

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