【菜鳥教程】Kafka消息隊列入門下(IDEA操作Kafka)

生產者

消息發送流程

Kafka 的 Producer 發送消息採用的是異步發送的方式。在消息發送的過程中,涉及到了兩個線程main 線程和 Sender 線程,以及一個線程共享變量RecordAccumulator
main 線程將消息發送給 RecordAccumulator,Sender 線程不斷從 RecordAccumulator 中拉取消息發送到 Kafka broker。

在這裏插入圖片描述

相關參數:
batch.size:只有數據積累到 batch.size 之後,sender 纔會發送數據。
linger.ms:如果數據遲遲未達到 batch.size,sender 等待 linger.time 之後就會發送數據。


異步生產

先啓動zookeeper和kafka集羣,不再贅述
創建一個maven工程,導入依賴,版本可以在kafka的libs目錄裏查到
在這裏插入圖片描述

<dependency>
	<groupId>org.apache.kafka</groupId>
	<artifactId>kafka-clients</artifactId>
	<version>2.1.1</version>
</dependency>

太慢了,我直接把linux的jar包拉過來了
在這裏插入圖片描述
在這裏插入圖片描述
這個時候纔有速度
在這裏插入圖片描述


遇到了一個坑,windows連不上kafka,要先修改kafka的server配置文件,把advertised.listeners那一行的註釋符號去掉 地址改爲你的虛擬機ipv4地址,然後我把剩下兩個改成9093和9094。
在這裏插入圖片描述


測試類:

public class MyProducer {

    public static void main(String[] args) {
        //生產者的配置信息
        Properties properties = new Properties();
        properties.put("bootstrap.servers","192.168.2.141:9092");//ip地址
        properties.put("acks","all");//ack級別
        properties.put("retries",2);//重試次數
        properties.put("batch.size",16384);//批次大小16KB
        properties.put("linger.ms",1);//等待時間 達到批次大小或等待時間發送
        properties.put("buffer.memory",33554432);//緩衝區大小32MB
        //key和value(都是String)類的序列化
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        //創建生產者對象
        Producer<String, String> producer = new KafkaProducer<>(properties);
        //發送數據
        for(int i=0;i<10;i++)
            producer.send(new ProducerRecord<>("data", "i want offer 202" + i));
        producer.close();
    }
}

重新啓動kafka集羣,使用consumer消費數據
在這裏插入圖片描述
運行idea的main方法,觀察輸出
在這裏插入圖片描述
可以發現數據並不是按0123456這樣的順序輸出的,在上一篇文章中創建data主題時指定了分區數爲2,因此0 2 4 6 8進入了一個分區,1 3 5 7 9進入了一個分區,分區數據是批量發送的,所以結果是1357902468。
在這裏插入圖片描述


可以使用ProducerConfig取代設置的常量字符串
在這裏插入圖片描述
剛纔的程序可改爲

public static void main(String[] args) {
        //生產者的配置信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.2.141:9092");//ip地址
        properties.put(ProducerConfig.ACKS_CONFIG,"all");//ack級別
        properties.put(ProducerConfig.RETRIES_CONFIG,2);//重試次數
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);//批次大小16KB
        properties.put(ProducerConfig.LINGER_MS_CONFIG,1);//等待時間 達到批次大小或等待時間發送
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);//緩衝區大小32MB
        //key和value(都是String)類的序列化
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //創建生產者對象
        Producer<String, String> producer = new KafkaProducer<>(properties);
        //發送數據
        for(int i=0;i<10;i++)
            producer.send(new ProducerRecord<>("data", "i want offer 202" + i));
        producer.close();
    }

昨天寫到了這裏…筆面試忙了兩天 現在繼續


帶回調函數的生產

主要是在send方法中多加了一個匿名函數

public class CallbackProducer {

    public static void main(String[] args) {
        //配置信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.2.141:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        //創建生產者對象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
        //生產數據
        for (int i = 0; i < 10; i++){
            int finalI = i;
            producer.send(new ProducerRecord<>("data", "面試求過--" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (e == null) {
                        System.out.println(finalI +": 分區是: " + recordMetadata.partition() + " offset: " + recordMetadata.offset());
                    }
                }
            });
        }
        producer.close();
    }
}

觀察linux客戶端,可見還是按分區批量生產數據的
在這裏插入圖片描述
觀察IDEA控制檯的輸出,也可以發現是按分區發送數據的,kafka默認使用range分區策略,所以會存在消費量不一致問題,分區1之前存有6個數據,所以offset從7開始。分區2是5個。
在這裏插入圖片描述


生產者分區策略測試

上一篇說過分區的原則
在這裏插入圖片描述
所以我們也可以指定分區爲0
在這裏插入圖片描述
此時數據會全部生產到分區0
在這裏插入圖片描述
不指定分區,會按照key的hash值計算
在這裏插入圖片描述
在這裏插入圖片描述


自定義分區器

通過實現Partitioner接口實現,return 1簡單模擬分區全爲1。

//自定義分區器
public class MyPartition implements Partitioner {

    @Override
    public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
        return 1;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

在這裏插入圖片描述
運行,可以發現分區全部在1
在這裏插入圖片描述


同步生產

由於 send 方法返回的是一個 Future 對象,根據 Futrue 對象的特點(獲取返回結果前會阻塞),我們也可以實現同步發送的效果,只需在調用 Future 對象的 get 方發即可。
在這裏插入圖片描述


消費者

消費數據

創建一個消費者測試類,啓動生產者後,觀察控制檯的輸出

public class MyConsumer {

    public static void main(String[] args) {
        //消費者配置信息
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.2.141:9092");
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);//開啓自動提交
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"1000");//自動提交時間爲1s
        //要反序列化的類
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"group1");
        //消費者對象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        //訂閱主題
        consumer.subscribe(Collections.singletonList("data"));
        while (true) {
            //獲取數據
            ConsumerRecords<String, String> messages = consumer.poll(Duration.ZERO);
            //解析並打印
            for (ConsumerRecord<String, String> message : messages) {
                System.out.println("key: " + message.key() + ",value: " + message.value());
            }
        }
    }
}

結果:
在這裏插入圖片描述


從頭消費數據

消費者從offset開始消費數據,如果需要從頭消費需要重置offset
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
但同時還需要改變消費者組,否則此時重置不會生效
此時消費者仍屬於消費者組group1,所以offset重置失敗
在這裏插入圖片描述
當消費者組改爲group2時,offset置0,相當於讀取到了全部數據
在這裏插入圖片描述


offset讀取問題

關閉自動提交
在這裏插入圖片描述
運行消費者,運行生產者生產10條數據,讀取到了10條數據
在這裏插入圖片描述
關閉生產者,重啓消費者,會將剛纔的10條數據自動讀出來
在這裏插入圖片描述
不要關閉消費者,再次運行生產者,也會讀取到新的10條數據
在這裏插入圖片描述
此時關閉生產者,重啓消費者,直接讀取到了20條數據
在這裏插入圖片描述
解釋:因爲關閉了自動提交,所以offset的最新值最終沒有更新
假設一開始是0,讀了10條信息,offset到了10,但是沒有更新所以還是0,下次重啓時從0直接讀到了10
然後又加入了10條數據,等於有了20條,再次重啓,從0讀到了20。


手動提交offset

雖然自動提交 offset 十分簡便,但由於其是基於時間提交的,開發人員難以把握
offset 提交的時機,因此 Kafka 還提供了手動提交 offset 的 API。
手動提交 offset 的方法有兩種:分別是 commitSync(同步提交)和 commitAsync(異步
提交)。
兩者的相同點是,都會將本次 poll 的一批數據最高的偏移量提交;不同點是,
commitSync 阻塞當前線程,一直到提交成功,並且會自動失敗重試(由不可控因素導致,也會出現提交失敗);而 commitAsync 則沒有失敗重試機制,故有可能提交失敗
同步:
在這裏插入圖片描述
異步:
在這裏插入圖片描述
也可以在異步方法通過匿名內部類處理錯誤
在這裏插入圖片描述

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