Spark Streaming + Kafka 另一利器 Kafka-spark-consumer 項目

在之前的文章中,曾經提到了,如何在使用 Kafka Direct API 處理消費時,將每個Partition的offset寫到Zookeeper中,並且在應用重新啓動或者應用升級時,可以通過讀取Zookeeper中的offset恢復之前的處理位置,進而繼續工作。而本篇文章則將要介紹另外一個 Spark Streaming + Kafka 的利器 – Kafka-spark-consumer 項目。


一、項目簡介

項目名稱:Kafka-spark-consumer
項目地址:https://github.com/dibbhatt/kafka-spark-consumer
在項目的 README.md 中,已經對這個項目有了一個詳細的介紹,此處就不對裏面的內容進行詳細的說明了,想了解的同學可自行去了解,總結一句話:牛。

我在這邊需要強調的是:Kafka-spark-consumer 項目在運行的過程中會把 topic 的每個 partition 的 offsets 寫到 Zookeeper 中,當我們對 Driver 程序進行升級 或者 需要重新啓動 Driver 程序的時候,Kafka-spark-consumer 可以從Zookeeper中恢複相關內容並繼續執行。


二、構建測試程序

程序的主要功能:從kafka中 kafka_direct topic 中處理消息,統計每個batch中單詞出現的次數。
2.1、添加依賴jar包,此處使用的maven方式。

<dependencies>
  <dependency>
    <groupId>dibbhatt</groupId>
    <artifactId>kafka-spark-consumer</artifactId>
    <version>1.0.6</version>
  </dependency>
</dependencies>
<repositories>
  <repository>
    <id>SparkPackagesRepo</id>
    <url>http://dl.bintray.com/spark-packages/maven</url>
  </repository>
</repositories>

由於需要下載相關依賴的jar包,所以我在下載的時候花了很長時間。

2.2、具體測試代碼,如下:

public class KafkaSparkConsumerTest{

    public static JavaStreamingContext createContext(){

        Properties props = new Properties();
        //Kafka所使用的Zookeeper的IP地址
        props.put("zookeeper.hosts", "192.168.1.151");
        //Kafka所使用的Zookeeper的端口
        props.put("zookeeper.port", "2181");
        //Kafka在Zookeeper中,保存broker 服務器的路徑
        props.put("zookeeper.broker.path", "/brokers");
        //配置目標 topic
        props.put("kafka.topic", "kafka_direct");
        //配置用來標識此程序作爲consumer的編號
        props.put("kafka.consumer.id", "54321");
        //配置用來存儲offset的Zookeeper
        props.put("zookeeper.consumer.connection", "192.168.1.151:2181");
        //配置存儲offset的基礎path
        props.put("zookeeper.consumer.path", "/kafka_spark_consumer");
        //********以下是可選參數 ******************/
        //配置是否強制從第一條消息開始處理,默認是從當時能獲取到的最後一條記錄開始處理
        props.put("consumer.forcefromstart", "true");
        props.put("consumer.fetchsizebytes", "1048576");
        props.put("consumer.fillfreqms", "250");
        props.put("consumer.backpressure.enabled", "true");

        SparkConf conf = new SparkConf().setMaster("local[4]").setAppName("KafkaSparkConsumerTest")
                .set("spark.streaming.receiver.writeAheadLog.enable", "false");

       JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(30));
       jsc.checkpoint("/checkpoint");

       /*
        * 由於ReceiverLauncher.launch的返回值爲JavaDStream<MessageAndMetadata>類型的,
        * 而我們現在所關心的是消息中的數據,所以直接調用了 MessageAndMetadata中的
        * getPayload()方法並構造爲 String類型的
        * 而MessageAndMetadata中包含了很多有用的內容,例如:consumer,topic,partition
        * ,offset,key,payload,而具體的含義從名稱上就可以看出來了。
        */
        JavaDStream<String> lines = ReceiverLauncher.launch(jsc, props,3, StorageLevel.MEMORY_ONLY()).map(new Function<consumer.kafka.MessageAndMetadata, String>() {

            @Override
            public String call(consumer.kafka.MessageAndMetadata v1) throws Exception {
                return new String( v1.getPayload());
            }
        });

        JavaDStream<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
                    public Iterable<String> call(
                           String event)
                            throws Exception {
                        return Arrays.asList(event);
                    }
                });

        JavaPairDStream<String, Integer> pairs = words
                .mapToPair(new PairFunction<String, String, Integer>() {

                    public Tuple2<String, Integer> call(
                            String word) throws Exception {
                        return new Tuple2<String, Integer>(
                                word, 1);
                    }
                });

        JavaPairDStream<String, Integer> wordsCount = pairs
                .reduceByKey(new Function2<Integer, Integer, Integer>() {
                    public Integer call(Integer v1, Integer v2)
                            throws Exception {
                        return v1 + v2;
                    }
                });

        wordsCount.print();

        return jsc;
    }

    public static void main(String[] args)  throws Exception{
        JavaStreamingContextFactory factory = new JavaStreamingContextFactory() {
            public JavaStreamingContext create() {
              return createContext();
            }
          };

        JavaStreamingContext jsc = JavaStreamingContext.getOrCreate("/checkpoint", factory);

        jsc.start();

        jsc.awaitTermination();
        jsc.close();
    }

}

2.3、準備測試環境。
當前 kafka_direct topic 中的各個partition的offset信息:
這裏寫圖片描述

當前ZooKeeper中的path信息:
這裏寫圖片描述
從截圖中可以看到 程序中使用到的 /kafka_spark_consumer 路徑目前還不存在。

2.4、運行Spark Streaming 程序,觀察命令打印及Zookeeper中的變化:
Spark Streaming 程序的輸出:
這裏寫圖片描述
從輸出中可以看到,它將我之前的測試數據一併打印了,非常符合程序中的設定 consumer.forcefromstart=true 的參數。

Zookeeper中的變化:
這裏寫圖片描述
從截圖中可以看到,已經將 kafka_direct 中的每個 partition 的offset 保存到了Zookeeper中了,其中最末節點的內容如下:
這裏寫圖片描述
這個截圖中的offset的值是40,正好與準備數據中的Kafka Manager中關於每個partition的offset的值是一樣的。

2.5、刪除checkpoint中的數據,並向kafka_direct中增加了四條消息,如下圖:
這裏寫圖片描述

2.6、再次運程Spark Streaming ,看其是否輸出了4個單詞的統計結果,如下圖:
這裏寫圖片描述
完全沒有問題!!!

發佈了29 篇原創文章 · 獲贊 18 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章