生產者
消息發送流程
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 則沒有失敗重試機制,故有可能提交失敗
同步:
異步:
也可以在異步方法通過匿名內部類處理錯誤