當大資料運動開始時,它主要集中在批處理上。分散式資料儲存和查詢工具(如MapReduce,Hive和Pig)都旨在分批處理資料而不是連續處理資料。企業每晚都會執行多個作業,從資料庫中提取資料,然後分析,轉換並最終儲存資料。最近,企業發現了分析和處理資料和事件的能力,而不是每隔幾個小時就會發生一次。然而,大多數傳統的訊息傳遞系統不能擴充套件以實時處理大資料。所以LinkedIn的工程師構建並開源Apache Kafka:一種分散式訊息傳遞框架,通過擴充套件商用硬體來滿足大資料的需求。
在過去幾年中,Apache Kafka已經出現,以解決各種情況。在最簡單的情況下,它可以是用於儲存應用程式日誌的簡單緩衝區。結合Spark Streaming等技術,它可用於跟蹤資料更改並對資料執行操作,然後將其儲存到最終目標。Kafka的預測模式使其成為檢測欺詐的有力工具,例如在信用卡交易發生時檢查信用卡交易的有效性,而不是等待數小時後的批處理。
這個由兩部分組成的教程介紹了Kafka,從如何在開發環境中安裝和執行它開始。您將瞭解Kafka的架構,然後介紹如何開發開箱即用的Apache Kafka訊息傳遞系統。最後,您將構建一個自定義生產者/消費者應用程式,通過Kafka伺服器傳送和使用訊息。在本教程的後半部分,您將學習如何對訊息進行分割槽和分組,以及如何控制Kafka消費者將使用哪些訊息。
什麼是Apache Kafka?
Apache Kafka是為大資料擴充套件而構建的訊息傳遞系統。與Apache ActiveMQ或RabbitMq類似,Kafka使構建在不同平臺上的應用程式能夠通過非同步訊息傳遞進行通訊。但Kafka與這些更傳統的訊息傳遞系統的關鍵方式不同:
- 它旨在通過新增更多伺服器來橫向擴充套件。
- 它為生產者和消費者流程提供了更高的吞吐量。
- 它可用於支援批處理和實時用例。
- 它不支援Java的面向訊息的中介軟體API JMS。
Apache Kafka的架構
在我們探索Kafka的架構之前,您應該瞭解它的基本術語:
- producer是將訊息釋出到主題的一個過程。
- consumer是訂閱一個或多個主題並且消費釋出到主題的訊息的過程。
- topic是訊息釋出的主題的名稱。
- broker是在一臺機器上執行的程式。
- cluster是一起工作的一組broker。
Apache Kafka的架構非常簡單,可以在某些系統中實現更好的效能和吞吐量。Kafka中的每個topic都像一個簡單的日誌檔案。當生產者釋出訊息時,Kafka伺服器會將其附加到其給定topic的日誌檔案的末尾。伺服器還分配一個偏移量,該偏移量是用於永久識別每條訊息的數字。隨著訊息數量的增加,每個偏移量的值增加; 例如,如果生產者釋出三條訊息,第一條訊息可能獲得偏移量1,第二條訊息偏移量為2,第三條偏移量為3。
當Kafka消費者首次啟動時,它將向伺服器傳送拉取請求,要求檢索偏移值大於0的特定topic的任何訊息。伺服器將檢查該topic的日誌檔案並返回三個新訊息。消費者將處理訊息,然後傳送偏移量大於3的訊息請求,依此類推。
在Kafka中,客戶端負責記住偏移計數和檢索訊息.Kafka伺服器不跟蹤或管理訊息消耗。預設情況下,Kafka伺服器將保留七天的訊息。伺服器中的後臺執行緒檢查並刪除七天或更早的訊息。只要訊息在伺服器上,消費者就可以訪問訊息。它可以多次讀取訊息,甚至可以按收到的相反順序讀取訊息。但是,如果消費者在七天之前未能檢索到訊息,那麼它將錯過該訊息。
LinkedIn和其他企業的生產使用表明,通過適當的配置,Apache Kafka每天能夠處理數百GB的資料。2011年,三位LinkedIn工程師使用基準測試來證明Kafka可以實現比ActiveMQ和RabbitMQ更高的吞吐量。
Apache Kafka快速設定和演示
我們將在本教程中構建一個自定義應用程式,但讓我們首先安裝和測試一個開箱即用的生產者和消費者的Kafka例項。
- 訪問Kafka下載頁面以安裝最新版本(撰寫本文時為0.9)。
- 將二進位制檔案解壓縮到一個
software/kafka
資料夾中。對於當前版本,它是software/kafka_2.11-0.9.0.0
。 - 將當前目錄更改為指向新資料夾。
- 通過執行以下命令啟動Zookeeper伺服器:
bin/zookeeper-server-start.sh config/zookeeper.properties
。 - 執行以下命令啟動Kafka伺服器:
bin/kafka-server-start.sh config/server.properties
。 - 建立一個可用於測試的測試topic:
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic javaworld
。 - 啟動一個簡單的控制檯使用者,它可以使用釋出到給定topic的訊息,例如
javaworld
:bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic javaworld --from-beginning
。 - 啟動一個簡單的生產者控制檯,可以將訊息釋出到測試topic:
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic javaworld
。 - 嘗試在生產者控制檯中輸入一條或兩條訊息。您的訊息應顯示在使用者控制檯中。
Apache Kafka的示例應用程式
您已經瞭解了Apache Kafka如何開箱即用。接下來,讓我們開發一個自定義生產者/消費者應用程式。生產者將從控制檯檢索使用者輸入,並將每個新行作為訊息傳送到Kafka伺服器。消費者將檢索給定topic的訊息並將其列印到控制檯。在這種情況下,生產者和消費者元件是您自己的kafka-console-producer.sh
和kafka-console-consumer.sh
。
讓我們從建立一個Producer.java
類開始。此客戶端類包含從控制檯讀取使用者輸入並將該輸入作為訊息傳送到Kafka伺服器的邏輯。
我們通過從java.util.Properties
類建立物件並設定其屬性來配置生產者。該ProducerConfig類定義了所有不同的屬性可用,但Kafka的預設值足以滿足大多數用途。對於預設配置,我們只需要設定三個必需屬性:
- BOOTSTRAP_SERVERS_CONFIG
- KEY_SERIALIZER_CLASS_CONFIG
- VALUE_SERIALIZER_CLASS_CONFIG
BOOTSTRAP_SERVERS_CONFIG (bootstrap.servers)
設定主機:埠對的列表,用於以host1:port1,host2:port2,...
格式建立與Kakfa叢集的初始連線。即使我們的Kafka叢集中有多個代理,我們也只需要指定第一個代理的值host:port
。Kafka客戶端將使用此值在代理上進行發現呼叫,該代理將返回叢集中所有代理的列表。最好在BOOTSTRAP_SERVERS_CONFIG
中指定多個代理,這樣如果第一個代理停止執行,客戶端將能夠嘗試其他代理。
Kafka伺服器需要byte[] key, byte[] value
格式化的訊息。Kafka的客戶端庫不是轉換每個鍵和值,而是允許我們使用更友好的型別String
和int
傳送訊息。庫將這些轉換為適當的型別。例如,示例應用程式沒有特定於訊息的key,因此我們將使用null作為key。對於值,我們將使用 String
,即使用者在控制檯上輸入的資料。
要配置訊息key,我們用org.apache.kafka.common.serialization.ByteArraySerializer
設定KEY_SERIALIZER_CLASS_CONFIG
的值。這是有效的,因為null不需要轉換為byte[]
。對於訊息值,我們為VALUE_SERIALIZER_CLASS_CONFIG
設定了org.apache.kafka.common.serialization.StringSerializer
,因為該類知道如何將String
轉換為 byte[]
。自定義鍵/值物件類似於StringSerializer
,Kafka為其他原語提供了序列化程式,例如int
和long
。為了使用自定義物件作為鍵或值,我們需要建立一個實現類org.apache.kafka.common.serialization.Serializer
。然後我們可以新增邏輯來將類序列化為byte[]
。我們還必須在我們的消費者程式碼中使用相應的反序列化器。
Kafka 生產者
在Properties
使用必要的配置屬性填充類之後,我們可以使用它來建立物件KafkaProducer
。每當我們要傳送的訊息後,該Kafka伺服器,我們將建立一個物件ProducerRecord
,並呼叫KafkaProducer
的send()
方法傳送訊息。ProducerRecord
有兩個引數:應該釋出訊息的topic的名稱,以及實際的訊息。使用生產者時,不要忘記呼叫該方法:Producer.close()
清單1. KafkaProducer
public class Producer {
private static Scanner in;
public static void main(String[] argv)throws Exception {
if (argv.length != 1) {
System.err.println("Please specify 1 parameters ");
System.exit(-1);
}
String topicName = argv[0];
in = new Scanner(System.in);
System.out.println("Enter message(type exit to quit)");
//Configure the Producer
Properties configProperties = new Properties();
configProperties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"localhost:9092");
configProperties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.ByteArraySerializer");
configProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
org.apache.kafka.clients.producer.Producer producer = new KafkaProducer<String, String>(configProperties);
String line = in.nextLine();
while(!line.equals("exit")) {
ProducerRecord<String, String> rec = new ProducerRecord<String, String>(topicName, line);
producer.send(rec);
line = in.nextLine();
}
in.close();
producer.close();
}
}
複製程式碼
配置訊息使用者
接下來,我們將建立一個訂閱topic的簡單消費者。每當向topic釋出新訊息時,它將讀取該訊息並將其列印到控制檯。消費者程式碼與生產者程式碼非常相似。我們首先建立一個物件java.util.Properties
,設定其特定於消費者的屬性,然後使用它來建立一個新物件KafkaConsumer
。ConsumerConfig類定義了我們可以設定的所有屬性。只有四個強制屬性:
BOOTSTRAP_SERVERS_CONFIG(bootstrap.servers)KEY_DESERIALIZER_CLASS_CONFIG(key.deserializer)VALUE_DESERIALIZER_CLASS_CONFIG(value.deserializer)GROUP_ID_CONFIG(bootstrap.servers)
正如我們為生產者類所做的那樣,我們將使用BOOTSTRAP_SERVERS_CONFIG
為消費者類配置主機/埠對。此配置允許我們以host1:port1,host2:port2,...
格式建立與Kakfa叢集的初始連線。正如我之前提到的,Kafka伺服器需要byte[]
鍵和byte[]
值格式的訊息,並且有自己的實現來序列化不同的型別byte[]
。正如我們對生產者所做的那樣,在消費者方面,我們將不得不使用自定義反序列化器轉換byte[]
回適當的型別。在示例應用程式的情況下,我們知道生產者正在使用`ByteArraySerializer`
key和StringSerializer
值。因此,在客戶端,我們需要使用org.apache.kafka.common.serialization.ByteArrayDeserializer
序列化key和org.apache.kafka.common.serialization.StringDeserializer
序列化值。將這些類為賦值KEY_DESERIALIZER_CLASS_CONFIG
和VALUE_DESERIALIZER_CLASS_CONFIG
將使消費者反序列化由生產者傳送的byte[]型別的資料。最後,我們需要設定值GROUP_ID_CONFIG
。這應該是字串格式的組名。我會在一分鐘內詳細解釋這個配置。現在,只需檢視具有四個強制屬性集的Kafka消費者:
清單2. KafkaConsumer
public class Consumer {
private static Scanner in;
private static boolean stop = false;
public static void main(String[] argv)throws Exception{
if (argv.length != 2) {
System.err.printf("Usage: %s <topicName> <groupId>\n",
Consumer.class.getSimpleName());
System.exit(-1);
}
in = new Scanner(System.in);
String topicName = argv[0];
String groupId = argv[1];
ConsumerThread consumerRunnable = new ConsumerThread(topicName,groupId);
consumerRunnable.start();
String line = "";
while (!line.equals("exit")) {
line = in.next();
}
consumerRunnable.getKafkaConsumer().wakeup();
System.out.println("Stopping consumer .....");
consumerRunnable.join();
}
private static class ConsumerThread extends Thread{
private String topicName;
private String groupId;
private KafkaConsumer<String,String> kafkaConsumer;
public ConsumerThread(String topicName, String groupId){
this.topicName = topicName;
this.groupId = groupId;
}
public void run() {
Properties configProperties = new Properties();
configProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
configProperties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
configProperties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
configProperties.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
configProperties.put(ConsumerConfig.CLIENT_ID_CONFIG, "simple");
//Figure out where to start processing messages from
kafkaConsumer = new KafkaConsumer<String, String>(configProperties);
kafkaConsumer.subscribe(Arrays.asList(topicName));
//Start processing messages
try {
while (true) {
ConsumerRecords<String, String> records = kafkaConsumer.poll(100);
for (ConsumerRecord<String, String> record : records)
System.out.println(record.value());
}
}catch(WakeupException ex){
System.out.println("Exception caught " + ex.getMessage());
}finally{
kafkaConsumer.close();
System.out.println("After closing KafkaConsumer");
}
}
public KafkaConsumer<String,String> getKafkaConsumer(){
return this.kafkaConsumer;
}
}
}
複製程式碼
消費者和消費者執行緒
將清單2中的消費者程式碼分為兩部分來確保Consumer
在退出之前關閉物件。我將依次描述每個類。首先,ConsumerThread
是一個內部類,它將topic名稱和組名稱作為其引數。在該類的run()
方法中,它建立一個具有適當屬性的KafkaConsumer
物件。它通過呼叫kafkaConsumer.subscribe()
方法訂閱topic,然後每100毫秒輪詢Kafka伺服器以檢查topic中是否有任何新訊息。它將遍歷任何新訊息的列表並將其列印到控制檯。
在Consumer
類中,我們建立一個新物件,並在另一個ConsumerThread
執行緒中啟動它。在ConsumerThead
開始一個無限迴圈,並保持輪詢新訊息的topic。同時在Consumer
類中,主執行緒等待使用者進入exit
控制檯。一旦使用者進入退出,它就會呼叫該KafkaConsumer.wakeup()
方法,導致KafkaConsumer
停止輪詢新訊息並丟擲一個WakeupException
。然後,我們可以通過呼叫kafkaConsumer
的close()
方法關閉KafkaConsumer
。
執行該應用程式
要測試此應用程式,您可以從IDE執行清單1和清單2中的程式碼,也可以按照以下步驟操作:
- 通過執行以下命令下載示例程式碼KafkaAPIClient :
git clone https://github.com/sdpatil/KafkaAPIClient.git
. - 編譯程式碼並使用以下命令建立胖JAR :
mvn clean compile assembly:single
. - 啟動消費者:
java -cp target/KafkaAPIClient-1.0-SNAPSHOT-jar-with-dependencies.jar com.spnotes.kafka.simple.Consumer test group1
。 - 啟動生產者:
java -cp target/KafkaAPIClient-1.0-SNAPSHOT-jar-with-dependencies.jar com.spnotes.kafka.simple.Producer test
。 - 在生產者控制檯中輸入訊息,然後檢查該訊息是否出現在使用者中。試試幾條訊息。
- 鍵入
exit
消費者和生產者控制檯以關閉它們。
第1部分的結論
在本教程的前半部分,您已經瞭解了使用Apache Kafka進行大資料訊息傳遞的基礎知識,包括Kafka的概念性概述,設定說明以及如何使用Kafka配置生產者/消費者訊息傳遞系統。
正如您所見,Kafka的架構既簡單又高效,專為效能和吞吐量而設計。在第2部分中,我將介紹一些使用Kafka進行分散式訊息傳遞的更高階技術,從使用分割槽細分主題開始。我還將演示如何管理訊息偏移以支援不同的用例。
英文原文:www.javaworld.com/article/306…
公眾號:銀河系1號
聯絡郵箱:public@space-explore.com
(未經同意,請勿轉載)
微信掃一掃關注該公眾號