初次邂逅Kafka生產者

Kafka生產者

客戶端開發

客戶端開發一般包含以下幾個基本流程:

  1. 配置生產者客戶端參數及創建生產者實例;
  2. 構建待發送的消息;
  3. 發送消息;
  4. 關閉生產者實例。

示例代碼:

public class KafkaProducerAnalysis {
    public static final String brokerList = "localhost:9092";
    public static final String topic = "topic-demo";
    //初始化生產者配置
    public static Properties initConfig() {
        Properties pros = new Properties();
        props.put(“bootstrap.servers", brokerList);
        props.put("key.serializer",
                 "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer",
                 "org.apache.kafka.common.serialization.StringSerializer");
        props.put("client.id", "producer.client.id.demo");
        
        return props;
    }
                  
    public static void main() {
        Properties props = initConfig();
        //創建生產者實例
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);
        
        //構建待發送的消息
        ProducerRecord<String, String> record = 
            new ProducerRecord<>(topic, "hello, Kafka!");
        
        try {
            //發送消息
            producer.send(record);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}

消息的格式

  • topic:主題

  • partition:分區號

  • headers:消息頭

  • key:鍵,不僅是消息的附加信息,而且可以用來計算分區號以發送消息至特定的分區

  • value:值,消息體,一般不爲空,爲空則表示特定的消息-墓碑消息。

  • timestamp:消息的時間戳,包括CreateTime和LogAppendTime兩種類型,分別表示消息的創建時間和消息追加到日誌文件的時間。

發送消息

  • ProducerRecord的創建有一系列重載的方法,最簡單和最常用的是:
public ProducerRecord(String top, V value);

使用這種方法相當於將其他參數都置爲null。

  • 發送消息有2個重載的方法,如下:
public Future<RecordMetadata> send(ProducerRecord<K, V> record)
public Future<RecordMetadata> send(ProducerRecord<K, V> record,
                                  Callback callback)
  • Kafka有以下幾種消息發送方式
  1. 發後即忘

  2. 同步發送消息

  3. 異步發送消息

    發後即忘

    上文中用法就是發後即忘,生產者只負責發送消息而不關心消息是否到達。通常消息可以順利發送,不過在某些異常情況下會有消息丟失的情況。這種消息發送方式性能最高,但可靠性也最差。

    同步發送消息

    注意KafkaProducer的send()方法返回值是Future類型,則可以利用Futrure對象實現同步發送消息,示例如下:

    try {
        producer.send(record).get();
    } catch (ExcutionException | InterruptedException e) {
        e.pringStackTrace();
    }
    

    send()方法本身是異步的,其返回值Future對象可以使調用方稍後獲得發送結果,例子中調用了get()方法來阻塞等待Kafka的響應,直到消息發送成功或發生異常。

    同步發送方式可靠性高,消息要麼發送成功,要麼發生異常,如果發生異常,可以在捕獲後進行相應處理,不會像發後即忘方式出現消息丟失。但是同步發送的缺點也很明顯,性能較差,因爲消息的發送是阻塞的,需要等待上一條消息發送完才能發送下一條。

    異步發送消息

    異步發送消息一般使用第二個重載的方法,入參中指定一個Callback的回調函數,Kafka在返回響應時調用該函數來實現異步的發送確認,要麼發送成功,要麼發生異常。示例如下:

    producer.send(record, new Callback() {
        @Override
        public void onCompletion(RecordMetadata metadata, Exception exception) {
            if (exception != null) {
                //處理異常
                ...
            } else {
                System.out.println(metadata.topic() + "-" +
                                  metadata.partition() + ":" + metadata.offset());
            }
        }
    })
    

    另外需要注意,對於同一個分區而言,如果消息1在消息2之前發送,那麼Kafka可以保證callback1在callback2之前調用,即:回調函數可以保證消息的分區有序性。

    producer.send(record1, callback1);
    producer.send(record2, callback2);
    
  • 關閉生產者

    Kafka提供close()方法來關閉生產者以釋放資源。

    public void close();
    public void close(long timeout, TimeUnit timeUnit);
    

    close()方法會阻塞等待之前所有的請求完成後再關閉生產者。調用帶超時時間的close()方法會等待timeout時間,用來完成請求,然後強行退出。一般無參的close()方法使用較多。

消息通過send()方法發往broker過程中,通常會經過攔截器、序列化器和分區器等。

3xSly6.png

分區器

若消息中沒有指定partition字段,則需要依賴分區器,根據key字段來計算partition的值。

Kafka中提供的默認分區器是:org.apache.kafka.producer.internals.DefaultPartitioner,它實現了org.apache.kafka.client.producer.Partitioner 接口,該接口中定義了兩個方法:

public int partition(String topic, Object key, byte[] keyBytes,
                    Object value, byte[] valueBytes, Cluster cluster);
public void close();
  1. 輪詢策略

    也成Round-robin策略,即順序分配消息到各個分區。如一個主題下有3個分區,那麼第一條消息發送到分區0,第二條消息發送到分區1,第三條消息發送到分區2,以此類推。

    這就是所謂的輪序策略。輪詢策略是Kafka的Java生產者API默認提供的分區策略。

    輪詢策略有非常優秀的負載均衡表現,它總能保證消息最大限度地被平均分配到所有分區上,所以它是默認情況下最合理的分區策略,這也是最常用的分區策略

  2. 按消息鍵保序策略

    Kafka允許爲每條消息定義消息鍵,簡稱爲key。一旦消息被定義了key,那麼就可以保證同一個key的所有消息進入相同的分區中,由於每個分區中的消息都是有順序的,所以該策略爲按消息鍵保序策略。

    該策略實現方法也相對簡單,只需兩行代碼:

    List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
    return Math.abs(key.hashCode()) % partitions.size();
    

    Kafka的默認分區策略實際上同時實現了兩種策略

    • 如果指定了key,那麼默認實現按消息鍵保序策略;

    • 如果沒有指定key,則使用輪詢策略。

  3. 自定義分區策略

    用戶也可以根據自身的業務需要,自定義自己的分區策略,比如一個大體量的數據中心需要大規模的Kafka集羣來進行跨城市、跨地域的數據交換,針對這種場景,可以使用基於地理位置的分區策略。

    比如針對南方用戶產生的消息和針對北方用戶產生的消息需要分別投放到相應的Broker中,我們就可以根據Broker所在的IP地址實現定製化的分區策略,如以下代碼:

    List<PartitionInfo> parititons = Cluster.partitionForTopic(topic);
    return partitions.stream().
        		filter(p -> isSouth(p.leader().host())).
        		map(Partition::partition).
        		findAny().
        		get();
    

    小結

    分區是實現負載均衡以及高吞吐量的關鍵,故在生產者這一端就要仔細分析合適的分區策略,避免造成消息數據的“傾斜”,使得某些分區成爲性能瓶頸,進而引發下游數據消費的性能下降。

生產者攔截器

生產者攔截器主要可以提供以下幾種功能:

  • 在消息發送前做一些準備工作,如按照某個規則過濾部分消息、修改消息內容等;
  • 在發送回調消息前做些定製化需求,比如統計類工作。

使用生產者攔截器,需要實現org.apache.kafka.clients.producer.ProducerInterceptor接口,該接口包含3個方法:

public ProducerRecord<K, V> onSend(ProducerRecord<K, V> record);
public void onAcknowledgement(RecordMetadata metadata, Exception exception);
public void close();
  • onSend()方法會在消息序列化和計算分區之前被調用,可以在該方法中對消息進行定製化操作。

  • onAcknowledgement()方法會在消息成功提交或發送失敗之後被調用。且該調用要早於用戶設定的callback。注意:該方法和onSend()方法不在一個線程裏,而是運行在Producer的I/O線程中,所以實現邏輯應該越簡單越好,否則會影響消息發送速度。

主要適用場景:生產者攔截器可以應用於包括客戶端監控、端到端系統性能檢測、消息審計等多種功能在內的場景。

總結

  1. 首先,我們瞭解了生產者在客戶端開發時的基本步驟;

  2. 在羅列了Kafka消息的基本字段屬性後,介紹了消息發送的幾種方式:發後即忘、同步發送和異步發送,並介紹了各自的優缺點;

  3. 最後我們根據消息發送流程分別介紹了分區器與生產者攔截器,知曉了默認分區策略以及其他幾種常用分區策略。在消息發送前或返回提交時做相應處理,可以依靠生產者攔截器。

以上就是關於Kafka生產者的相關基礎知識,至此,各位小夥伴們應該對Kafka生產者有了初步瞭解,希望可以幫助到大家,謝謝!

參考資料:
《深入理解Kafka核心設計與實踐原理》
極客時間-《Kafka核心技術與實戰》

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