Kafka學習筆記(二) :初探Kafka

三分青年發表於2018-03-26

看完上一篇,相信大家對訊息系統以及Kafka的整體構成都有了初步瞭解,學習一個東西最好的辦法,就是去使用它,今天就讓我們一起窺探一下Kafka,並完成自己的處女作。

訊息在Kafka中的歷程

雖然我們掌握東西要一步一步來,但是我們在大致瞭解了一個東西后,會有利於我們對它的理解和學習,所以我們可以先來看一下一條訊息從發出到最後被訊息者接收到底經歷了什麼?

Kafka學習筆記(二) :初探Kafka

上圖簡要的說明了訊息在Kafka中的整個流轉過程(假設已經部署好了整個Kafka系統,並建立了相應的Topic,分割槽等細節後續再單獨講):

  • 1.訊息生產者將訊息釋出到具體的Topic,根據一定演算法或者隨機被分發到具體的分割槽中;
  • 2.根據實際需求,是否需要實現處理訊息邏輯;
  • 3.若需要,則實現具體邏輯後將結果釋出到輸出Topic;
  • 4.消費者根據需求訂閱相關Topic,並消費訊息;

總的來說,怎麼流程還是比較清晰和簡單的,下面就跟我一起來練習Kafka的基本操作,最後實現一個單詞計數的小demo。

基礎操作

以下程式碼及相應測試在以下環境測試通過:Mac OS + JDK1.8,Linux系統應該也能跑通,Windows有興趣的同學可以去官網下載相應版本進行相應的測試練習。

下載Kafka

Mac系統同學可以使用brew安裝:

brew install kafka
複製程式碼

Linux系統同學可以從官網下載原始碼解壓,也可以直接執行以下命令:

cd 
mkdir test-kafka && cd test-kafka
curl -o kafka_2.11-1.0.1.tgz http://mirrors.tuna.tsinghua.edu.cn/apache/kafka/1.0.1/kafka_2.11-1.0.1.tgz
tar -xzf kafka_2.11-1.0.1.tgz
cd kafka_2.11-1.0.1

複製程式碼

啟動

Kafka使用Zookeeper來維護叢集資訊,所以這裡我們先要啟動Zookeeper,Kafka與Zookeeper的相關聯絡跟結合後續再深入瞭解,畢竟不能一口吃成一個胖子。

bin/zookeeper-server-start.sh config/zookeeper.properties
複製程式碼

接著我們啟動一個Kafka Server節點:

bin/kafka-server-start.sh config/server.properties
複製程式碼

這時候Kafka系統已經算是啟動起來了。

建立Topic

在一切就緒之後,我們要開始做極其重要的一步,那就是建立Topic,Topic是整個系統流轉的核心,另外Topic本身也包含著很多複雜的引數,比如複製因子個數,分割槽個數等,這裡為了從簡,我們將對應的引數都設為1,方便大家測試:

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic kakfa-test
複製程式碼

其中引數的具體含義:

屬性 功能
--create 代表建立Topic
--zookeeper zookeeper叢集資訊
--replication-factor 複製因子
--partitions 分割槽資訊
--topic Topic名稱

這時候我們已經建立好了一個叫kakfa-test的Topic了。

向Topic傳送訊息

在有了Topic後我們就可以向其傳送訊息:

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic kakfa-test
複製程式碼

然後我們向控制檯輸入一些訊息:

this is my first test kafka
so good
複製程式碼

這時候訊息已經被髮布在kakfa-test這個主題上了。

從Topic獲取訊息

現在Topic上已經有訊息了,現在可以從中獲取訊息被消費:

bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic kafka-test --from-beginning
複製程式碼

這時候我們可以在控制檯看到:

this is my first test kafka
so good
複製程式碼

至此我們就測試了最簡單的Kafka Demo,希望大家能自己動手去試試,雖然很簡單,但是這能讓你對整個Kafka流程能更熟悉。

WordCount

下面我們來利用上面的一些基本操作來實現一個簡單WordCount程式,它具備以下功能:

  • 1.支援片語持續輸入,即生產者不斷生成訊息;
  • 2.程式自動從輸入Topic中獲取原始資料,然後經過處理,將處理結果釋出在計數Topic中;
  • 3.消費者可以從計數Topic獲取相應的WordCount的結果;

1.啟動kafka

與上文的啟動一樣,按照其操作即可。

2.建立輸入Topic

bin/kafka-topics.sh --zookeeper localhost:2181 --create --topic kafka-word-count-input --partitions 1 --replication-factor 1
複製程式碼

3.向Topic輸入訊息

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic kafka-word-count-input
複製程式碼

4.流處理邏輯

這部分內容是整個例子的核心,這部分程式碼有Java 8+和Scala版本,個人認為流處理用函式式語法表達的更加簡潔清晰,推薦大家用函式式的思維去嘗試寫以下,發現自己再也不想寫Java匿名內部類這種語法了。

我們先來看一個Java 8的版本:

public class WordCount {
    public static void main(String[] args) throws Exception {
        Properties props = new Properties();
        props.put(StreamsConfig.APPLICATION_ID_CONFIG, "kafka-word-count");
        props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

        final StreamsBuilder builder = new StreamsBuilder();
        KStream<String, String> source = builder.<String, String>stream("kafka-word-count-input");
        Pattern pattern = Pattern.compile("\\W+");
        source
           .flatMapValues(value -> Arrays.asList(pattern.split(value.toLowerCase(Locale.getDefault()))))
           .groupBy((key, value) -> value)
           .count(Materialized.<String, Long, KeyValueStore<Bytes, byte[]>>as("counts-store")).mapValues(value->Long.toString(value))
           .toStream()
           .to("kafka-word-count-output");
        final KafkaStreams streams = new KafkaStreams(builder.build(), props);
        streams.start();
    }
}
複製程式碼

是不是很驚訝,用java也能寫出如此簡潔的程式碼,所以說如果有適用場景,推薦大家嘗試的用函式式的思維去寫寫java程式碼。

我們再來看看Scala版本的:


object WordCount {
  def main(args: Array[String]) {
    val props: Properties = {
      val p = new Properties()
      p.put(StreamsConfig.APPLICATION_ID_CONFIG, "kafka-word-count")
      p.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092")
      p.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String.getClass)
      p.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String.getClass)
      p
    }

    val builder: StreamsBuilder = new StreamsBuilder()
    val source: KStream[String, String] = builder.stream("kafka-word-count-input")
    source
      .flatMapValues(textLine => textLine.toLowerCase.split("\\W+").toIterable.asJava)
      .groupBy((_, word) => word)
      .count(Materialized.as[String, Long, KeyValueStore[Bytes, Array[Byte]]]("counts-store")).toStream.to("kafka-word-count-output")
    val streams: KafkaStreams = new KafkaStreams(builder.build(), props)
    streams.start()
  }
}
複製程式碼

可以發現使用Java 8函式式風格編寫的程式碼已經跟Scala很相似了。

5.啟動處理邏輯

很多同學電腦上並沒有裝sbt,所以這裡演示的利用Maven構建的Java版本,具體執行步驟請參考戳這裡kafka-word-count上的說明。

6.啟動消費者程式

最後我們啟動消費者程式,並在生產者中輸入一些單詞,比如:

Kafka學習筆記(二) :初探Kafka

最後我們可以在消費者程式中看到以下輸出:

bin/kafka-console-consumer.sh --topic kafka-word-count-output --from-beginning --bootstrap-server localhost:9092  --property print.key=true
複製程式碼

kafka-word-count-output

總結

本篇文章主要是講解了Kafka的基本執行過程和一些基礎操作,但這是我們學習一個東西必不可少的一步,只有把基礎紮實好,才能更深入的去了解它,理解它為什麼這麼設計,我在這個過程中也遇到很多麻煩,所以還是希望大家能夠自己動手去實踐一下,最終能收穫更多。

相關文章