kafka的安裝與使用
廣告系統設計與實現(九)
9.1 Kafka 基礎知識
9.1.1 訊息系統
點對點訊息系統:生產者傳送一條訊息到queue,一個queue可以有很多消費者,但是一個訊息只能被一個消費者接受,當沒有消費者可用時,這個訊息會被儲存直到有 一個可用的消費者,所以Queue實現了一個可靠的負載均衡。
釋出訂閱訊息系統:釋出者傳送到topic的訊息,只有訂閱了topic的訂閱者才會收到訊息。topic實現了釋出和訂閱,當你釋出一個訊息,所有訂閱這個topic的服務都能得到這個訊息,所以從1到N個訂閱者都能得到這個訊息的拷貝。
9.1.2 kafka術語
訊息由producer產生,訊息按照topic歸類,併傳送到broker中,broker中儲存了一個或多個topic的訊息,consumer通過訂閱一組topic的訊息,通過持續的poll操作從broker獲取訊息,並進行後續的訊息處理。
Producer :訊息生產者,就是向broker髮指定topic訊息的客戶端。
Consumer :訊息消費者,通過訂閱一組topic的訊息,從broker讀取訊息的客戶端。
Broker :一個kafka叢集包含一個或多個伺服器,一臺kafka伺服器就是一個broker,用於儲存producer傳送的訊息。一個broker可以容納多個topic。
Topic :每條傳送到broker的訊息都有一個類別,可以理解為一個佇列或者資料庫的一張表。
Partition:一個topic的訊息由多個partition佇列儲存的,一個partition佇列在kafka上稱為一個分割槽。每個partition是一個有序的佇列,多個partition間則是無序的。partition中的每條訊息都會被分配一個有序的id(offset)。
Offset:偏移量。kafka為每條在分割槽的訊息儲存一個偏移量offset,這也是消費者在分割槽的位置。kafka的儲存檔案都是按照offset.kafka來命名,位於2049位置的即為2048.kafka的檔案。比如一個偏移量是5的消費者,表示已經消費了從0-4偏移量的訊息,下一個要消費的訊息的偏移量是5。
Consumer Group (CG):若干個Consumer組成的集合。這是kafka用來實現一個topic訊息的廣播(發給所有的consumer)和單播(發給任意一個consumer)的手段。一個topic可以有多個CG。topic的訊息會複製(不是真的複製,是概念上的)到所有的CG,但每個CG只會把訊息發給該CG中的一個consumer。如果需要實現廣播,只要每個consumer有一個獨立的CG就可以了。要實現單播只要所有的consumer在同一個CG。用CG還可以將consumer進行自由的分組而不需要多次傳送訊息到不同的topic。
假如一個消費者組有兩個消費者,訂閱了一個具有4個分割槽的topic的訊息,那麼這個消費者組的每一個消費者都會消費兩個分割槽的訊息。消費者組的成員是動態維護的,如果新增或者減少了消費者組中的消費者,那麼每個消費者消費的分割槽的訊息也會動態變化。比如原來一個消費者組有兩個消費者,其中一個消費者因為故障而不能繼續消費訊息了,那麼剩下一個消費者將會消費全部4個分割槽的訊息。
9.1.3 kafka安裝和使用
在Windows安裝執行Kafka:https://blog.csdn.net/weixin_38004638/article/details/91893910
9.1.4 kafka執行
一次寫入,支援多個應用讀取,讀取資訊是相同的
kafka-study.pom
<dependencies> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.12</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.24</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.0</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>
Producer生產者
傳送訊息的方式,只管傳送,不管結果:只呼叫介面傳送訊息到 Kafka 伺服器,但不管成功寫入與否。由於 Kafka 是高可用的,因此大部分情況下訊息都會寫入,但在異常情況下會丟訊息
同步傳送:呼叫 send() 方法返回一個 Future 物件,我們可以使用它的 get() 方法來判斷訊息傳送成功與否
非同步傳送:呼叫 send() 時提供一個回撥方法,當接收到 broker 結果後回撥此方法
public class MyProducer { private static KafkaProducer<String, String> producer; //初始化 static { Properties properties = new Properties(); //kafka啟動,生產者建立連線broker的地址 properties.put("bootstrap.servers", "127.0.0.1:9092"); //kafka序列化方式 properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //自定義分割槽分配器 properties.put("partitioner.class", "com.imooc.kafka.CustomPartitioner"); producer = new KafkaProducer<>(properties); } /** * 建立topic:.\bin\windows\kafka-topics.bat --create --zookeeper localhost:2181 * --replication-factor 1 --partitions 1 --topic kafka-study * 建立消費者:.\bin\windows\kafka-console-consumer.bat --bootstrap-server localhost:9092 * --topic imooc-kafka-study --from-beginning */ //傳送訊息,傳送完後不做處理 private static void sendMessageForgetResult() { ProducerRecord<String, String> record = new ProducerRecord<>("kafka-study", "name", "ForgetResult"); producer.send(record); producer.close(); } //傳送同步訊息,獲取傳送的訊息 private static void sendMessageSync() throws Exception { ProducerRecord<String, String> record = new ProducerRecord<>("kafka-study", "name", "sync"); RecordMetadata result = producer.send(record).get(); System.out.println(result.topic());//imooc-kafka-study System.out.println(result.partition());//分割槽為0 System.out.println(result.offset());//已傳送一條訊息,此時偏移量+1 producer.close(); } /** * 建立topic:.\bin\windows\kafka-topics.bat --create --zookeeper localhost:2181 * --replication-factor 1 --partitions 3 --topic kafka-study-x * 建立消費者:.\bin\windows\kafka-console-consumer.bat --bootstrap-server localhost:9092 * --topic kafka-study-x --from-beginning */ private static void sendMessageCallback() { ProducerRecord<String, String> record = new ProducerRecord<>("kafka-study-x", "name", "callback"); producer.send(record, new MyProducerCallback()); //傳送多條訊息 record = new ProducerRecord<>("kafka-study-x", "name-x", "callback"); producer.send(record, new MyProducerCallback()); producer.close(); } //傳送非同步訊息 //場景:每條訊息傳送有延遲,多條訊息傳送,無需同步等待,可以執行其他操作,程式會自動非同步呼叫 private static class MyProducerCallback implements Callback { @Override public void onCompletion(RecordMetadata recordMetadata, Exception e) { if (e != null) { e.printStackTrace(); return; } System.out.println("*** MyProducerCallback ***"); System.out.println(recordMetadata.topic()); System.out.println(recordMetadata.partition()); System.out.println(recordMetadata.offset()); } } public static void main(String[] args) throws Exception { //sendMessageForgetResult(); //sendMessageSync(); sendMessageCallback(); } }
自定義分割槽分配器:決定訊息存放在哪個分割槽.。預設分配器使用輪詢存放,輪到已滿分割槽將會寫入失敗。
public class CustomPartitioner implements Partitioner { @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) { //獲取topic所有分割槽 List<PartitionInfo> partitionInfos = cluster.partitionsForTopic(topic); int numPartitions = partitionInfos.size(); //訊息必須有key if (null == keyBytes || !(key instanceof String)) { throw new InvalidRecordException("kafka message must have key"); } //如果只有一個分割槽,即0號分割槽 if (numPartitions == 1) {return 0;} //如果key為name,傳送至最後一個分割槽 if (key.equals("name")) {return numPartitions - 1;} return Math.abs(Utils.murmur2(keyBytes)) % (numPartitions - 1); } @Override public void close() {} @Override public void configure(Map<String, ?> map) {} }
啟動生產者傳送訊息,通過自定義分割槽分配器分配,查詢到topic資訊的value、partitioner
Kafka消費者(組)
* 自動提交位移 * 手動同步提交當前位移 * 手動非同步提交當前位移 * 手動非同步提交當前位移帶回撥 * 混合同步與非同步提交位移
public class MyConsumer { private static KafkaConsumer<String, String> consumer; private static Properties properties; //初始化 static { properties = new Properties(); //建立連線broker的地址 properties.put("bootstrap.servers", "127.0.0.1:9092"); //kafka反序列化 properties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); //指定消費者組 properties.put("group.id", "KafkaStudy"); } //自動提交位移:由consume自動管理提交 private static void generalConsumeMessageAutoCommit() { //配置 properties.put("enable.auto.commit", true); consumer = new KafkaConsumer<>(properties); //指定topic consumer.subscribe(Collections.singleton("kafka-study-x")); try { while (true) { boolean flag = true; //拉取資訊,超時時間100ms ConsumerRecords<String, String> records = consumer.poll(100); //遍歷列印訊息 for (ConsumerRecord<String, String> record : records) { System.out.println(String.format( "topic = %s, partition = %s, key = %s, value = %s", record.topic(), record.partition(), record.key(), record.value() )); //訊息傳送完成 if (record.value().equals("done")) { flag = false; } } if (!flag) { break; } } } finally { consumer.close(); } } //手動同步提交當前位移,根據需求提交,但容易傳送阻塞,提交失敗會進行重試直到丟擲異常 private static void generalConsumeMessageSyncCommit() { properties.put("auto.commit.offset", false); consumer = new KafkaConsumer<>(properties); consumer.subscribe(Collections.singletonList("kafka-study-x")); while (true) { boolean flag = true; ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.println(String.format( "topic = %s, partition = %s, key = %s, value = %s", record.topic(), record.partition(), record.key(), record.value() )); if (record.value().equals("done")) { flag = false; } } try { //手動同步提交 consumer.commitSync(); } catch (CommitFailedException ex) { System.out.println("commit failed error: " + ex.getMessage()); } if (!flag) { break; } } } //手動非同步提交當前位移,提交速度快,但失敗不會記錄 private static void generalConsumeMessageAsyncCommit() { properties.put("auto.commit.offset", false); consumer = new KafkaConsumer<>(properties); consumer.subscribe(Collections.singletonList("kafka-study-x")); while (true) { boolean flag = true; ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.println(String.format( "topic = %s, partition = %s, key = %s, value = %s", record.topic(), record.partition(), record.key(), record.value() )); if (record.value().equals("done")) { flag = false; } } //手動非同步提交 consumer.commitAsync(); if (!flag) { break; } } } //手動非同步提交當前位移帶回撥 private static void generalConsumeMessageAsyncCommitWithCallback() { properties.put("auto.commit.offset", false); consumer = new KafkaConsumer<>(properties); consumer.subscribe(Collections.singletonList("kafka-study-x")); while (true) { boolean flag = true; ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.println(String.format( "topic = %s, partition = %s, key = %s, value = %s", record.topic(), record.partition(), record.key(), record.value() )); if (record.value().equals("done")) { flag = false; } } //使用java8函數語言程式設計 consumer.commitAsync((map, e) -> { if (e != null) { System.out.println("commit failed for offsets: " + e.getMessage()); } }); if (!flag) { break; } } } //混合同步與非同步提交位移 @SuppressWarnings("all") private static void mixSyncAndAsyncCommit() { properties.put("auto.commit.offset", false); consumer = new KafkaConsumer<>(properties); consumer.subscribe(Collections.singletonList("kafka-study-x")); try { while (true) { //boolean flag = true; ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.println(String.format( "topic = %s, partition = %s, key = %s, " + "value = %s", record.topic(), record.partition(), record.key(), record.value() )); //if (record.value().equals("done")) { flag = false; } } //手動非同步提交,保證效能 consumer.commitAsync(); //if (!flag) { break; } } } catch (Exception ex) { System.out.println("commit async error: " + ex.getMessage()); } finally { try { //非同步提交失敗,再嘗試手動同步提交 consumer.commitSync(); } finally { consumer.close(); } } } public static void main(String[] args) { //自動提交位移 generalConsumeMessageAutoCommit(); //手動同步提交當前位移 //generalConsumeMessageSyncCommit(); //手動非同步提交當前位移 //generalConsumeMessageAsyncCommit(); //手動非同步提交當前位移帶回撥 //generalConsumeMessageAsyncCommitWithCallback() //混合同步與非同步提交位移 //mixSyncAndAsyncCommit(); } }
先啟動消費者等待接收訊息,再啟動生產者傳送訊息,進行消費訊息
相關文章
- kafka的安裝及使用Kafka
- kafka 安裝到使用Kafka
- kafka安裝及使用Kafka
- Kafka SSL安裝與配置Kafka
- kafka的認識、安裝與配置Kafka
- kafka 安裝部署,使用教程Kafka
- 使用 Bitnami Helm 安裝 KafkaKafka
- kafka入門安裝和使用Kafka
- PHP 使用 Kafka 安裝拾遺PHPKafka
- kafka的內部實現、安裝和使用Kafka
- Kafka應用實戰——Kafka安裝及簡單使用Kafka
- Linux系統安裝和使用Kafka教程。LinuxKafka
- Linux 安裝kafkaLinuxKafka
- Kafka安裝-linuxKafkaLinux
- Linux安裝KafkaLinuxKafka
- IDM的安裝與使用
- nvm的安裝與使用
- Arthas的安裝與使用
- DrissionPage的安裝與使用
- Anaconda的安裝與使用
- Kafka 1.0.0的安裝使用以及一些命令Kafka
- mac環境canal+mysql+kafka的安裝及使用MacMySqlKafka
- PySpark與GraphFrames的安裝與使用Spark
- Kafka 的安裝及啟動Kafka
- 安裝Kafka叢集Kafka
- 4-kafka安裝Kafka
- 安裝測試kafkaKafka
- kafka和zookeeper安裝Kafka
- Kafka2.8安裝Kafka
- Android studio的安裝與使用Android
- KubernetesNginxIngress安裝與使用Nginx
- curl 安裝與使用
- MITMF安裝與使用MIT
- Supervisor 安裝與使用
- Samba安裝與使用Samba
- nacos安裝與使用
- Presto安裝與使用REST
- Kylin安裝與使用