spark streaming執行kafka資料來源

碼農阿益發表於2020-11-14

一、Kafka準備工作

Kafka的安裝,請看另外一文,一定要選擇和自己電腦上已經安裝的scala版本號一致才可以,本教程安裝的Spark版本號是1.6.2,scala版本號是2.10,所以,一定要選擇Kafka版本號是2.10開頭的。比如,到Kafka官網中,可以下載安裝檔案Kafka_2.10-0.10.1.0,前面的2.10就是支援的scala版本號,後面的0.10.1.0是Kafka自身的版本號。

下面,我們啟動Kafka。
請登入Linux系統(本教程統一使用hadoop使用者登入),開啟一個終端,輸入下面命令啟動Zookeeper服務:

cd /usr/local/kafka
./bin/zookeeper-server-start.sh config/zookeeper.properties

注意,執行上面命令以後,終端視窗會返回一堆資訊,然後就停住不動了,是Zookeeper伺服器啟動了,正在處於服務狀態。所以,千萬不要關閉這個終端視窗,一旦關閉,zookeeper服務就停止了,所以,不能關閉這個終端視窗。

cd /usr/local/kafka
bin/kafka-server-start.sh config/server.properties

同樣,執行上面命令以後,終端視窗會返回一堆資訊,然後就停住不動了,是Kafka伺服器啟動了,正在處於服務狀態。所以,千萬不要關閉這個終端視窗,一旦關閉,Kafka服務就停止了,所以,不能關閉這個終端視窗。

下面先測試一下Kafka是否可以正常使用。再另外開啟第三個終端,然後輸入下面命令建立一個自定義名稱為“wordsendertest”的topic(關於什麼是topic,請參考《Kafka的安裝和簡單例項測試》):

cd /usr/local/kafka
./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic wordsendertest
//這個topic叫wordsendertest,2181是zookeeper預設的埠號,partition是topic裡面的分割槽數,replication-factor是備份的數量,在kafka叢集中使用,這裡單機版就不用備份了
//可以用list列出所有建立的topics,來檢視上面建立的topic是否存在
./bin/kafka-topics.sh --list --zookeeper localhost:2181

這個名稱為“wordsendertest”的topic,就是專門負責採集傳送一些單詞的。
下面,我們需要用producer來產生一些資料,請在當前終端內繼續輸入下面命令:

./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic wordsendertest

上面命令執行後,你就可以在當前終端內用鍵盤輸入一些英文單詞,比如我們可以輸入:

hello world
hello teach
hello spark

這些單詞就是資料來源,這些單詞會被Kafka捕捉到以後傳送給消費者。我們現在可以啟動一個消費者,來檢視剛才producer產生的資料。請另外開啟第四個終端,輸入下面命令:

cd /usr/local/kafka
./bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic wordsendertest --from-beginning

可以看到,螢幕上會顯示出如下結果,也就是剛才你在另外一個終端裡面輸入的內容:

hello world
hello teach
hello spark

到這裡,與Kafka相關的準備工作就順利結束了。注意,所有這些終端視窗都不要關閉,要繼續留著後面使用。

二、Spark準備工作

Kafka和Flume等高階輸入源,需要依賴獨立的庫(jar檔案)。按照我們前面安裝好的Spark版本,這些jar包都不在裡面,為了證明這一點,我們現在可以測試一下。請開啟一個新的終端,然後啟動spark-shell:

cd /usr/local/spark
./bin/spark-shell

啟動成功後,在spark-shell中執行下面import語句:

scala> import org.apache.spark.streaming.kafka._
<console>:25: error: object kafka is not a member of package org.apache.spark.streaming
         import org.apache.spark.streaming.kafka._
                                           ^

你可以看到,馬上會報錯,因為找不到相關的jar包。所以,現在我們就需要下載spark-streaming-kafka_2.10.jar。
現在請在Linux系統中,開啟一個火狐瀏覽器,請點選這裡訪問Spark官網,裡面有提供spark-streaming-kafka_2.10.jar檔案的下載,其中,2.10表示scala的版本。這個下載頁面會列出spark-streaming-kafka_2.10.jar的很多版本,我們這裡選擇1.6.2版本(因為本教程安裝的Spark版本是1.6.2),你可以點選1.6.2版本的按鈕,進入spark-streaming-kafka_2.10-1.6.2.jar的下載頁面,點選下載。下載後的檔案會被預設儲存在當前Linux登入使用者的下載目錄下,本教程統一使用hadoop使用者名稱登入Linux系統,所以,檔案下載後會被儲存到“/home/hadoop/下載”目錄下面。現在,我們就把這個檔案複製到Spark目錄的lib目錄下。請新開啟一個終端,輸入下面命令:

cd /usr/local/spark/lib
mkdir kafka
cd ~
cd 下載
cp ./spark-streaming-kafka_2.10-1.6.2.jar /usr/local/spark/lib/kafka

這樣,我們就把spark-streaming-kafka_2.10-1.6.2.jar檔案拷貝到了“/usr/local/spark/lib/kafka”目錄下。
下面開始的過程,讓筆者消耗了2個白天和2個黑夜,測試了網路上各種解決方案,反覆失敗,最終,只能自己進行“蠻力測試”,下載各種版本進行嘗試,最終,得以解決,解決的祕訣是,第一個祕訣是,凡是遇到“java.lang.NoSuchMethodError”這種錯誤,一定是由版本不一致導致的,也就是spark、scala、spark streaming、Kafka這幾個軟體的版本不一致,所以,必須保證它們版本的一致性。比如,筆者電腦安裝了Spark1.6.2版本,它包含的scala版本是2.10.5,那麼,與之對應的straming的版本也必須是spark-streaming_2.10-1.6.2.jar,對應的Kafka也必須是spark-streaming-kafka_2.10-1.6.2.jar。第二個祕訣是,凡是遇到“Class Not Found”這種錯誤,一般是由缺少jar包引起的。到底缺少什麼jar包,筆者在網路上找不到解決方案,也只能靠自己“蒙”,所以,下面筆者測試通過的方法,只是自己反覆測試兩天後“蒙”對了,但是,也說不出是什麼道理。請你按照下面操作即可。

還需要在Linux系統中,開啟火狐瀏覽器,到網路上下載另外一個檔案spark-streaming_2.10-1.6.2.jar(下載地址),其中,2.10是scala版本號,1.6.2是Spark版本號。
spark-streaming_2.10-1.6.1.jar下載成功以後是被放到當前Linux登入使用者的下載目錄下,本教程統一使用hadoop使用者名稱登入Linux系統,所以,檔案下載後會被儲存到“/home/hadoop/下載”目錄下面。現在,我們就把這個檔案複製到Spark目錄的lib目錄下。請新開啟一個終端,輸入下面命令:

cd /usr/local/spark/lib
cd ~
cd 下載
cp ./spark-streaming_2.10-1.6.1.jar /usr/local/spark/lib/kafka

還沒完,下面還要繼續把Kafka安裝目錄的lib目錄下的所有jar檔案複製到“/usr/local/spark/lib/kafka”目錄下,請在終端中執行下面命令:

cd /usr/local/kafka/libs
ls
cp ./* /usr/local/spark/lib/kafka

為什麼要拷貝所有的jar過來,因為筆者遇到錯誤以後,知道是因為缺少jar包,但是,實在不知道是缺少什麼jar包,所以,就全部拷貝過來了。但是,全部拷貝過來以後,有些jar包會和spark中已經有的jar包發生衝突,程式一執行就會出現一堆的錯誤,根據錯誤提示資訊,筆者大概猜測是因為衝突引起的,所以,就刪除一些可能引起衝突的jar包,刪除一些後,再測試程式,再出錯,再刪除一批,最後測試通過了,就發現,需要把下面這些jar包刪除掉,這樣才不會出現錯誤:

cd /usr/local/kafka/libs
ls
rm log4j*
rm jackson*

上面這些方法純粹是靠蠻力反覆測試才成功的,沒有找到問題的本質,也沒有網路資料可以幫筆者解決。但是,起碼通過這種方式,程式可以順利執行了。

現在,我們需要配置spark-env.sh檔案,讓spark能夠在啟動的時候找到spark-streaming-kafka_2.10-1.6.2.jar等5個jar檔案。命令如下:

cd /usr/local/spark/conf
vim spark-env.sh

使用vim編輯器開啟了spark-env.sh檔案,因為這個檔案之前已經反覆修改過,目前裡面的前面幾行的內容應該是這樣的:

export SPARK_CLASSPATH=$SPARK_CLASSPATH:/usr/local/spark/lib/hbase/*
export SPARK_DIST_CLASSPATH=$(/usr/local/hadoop/bin/hadoop classpath)

我們只要簡單修改一下,把“/usr/local/spark/lib/kafka/*“增加進去,修改後的內容如下:

export SPARK_CLASSPATH=$SPARK_CLASSPATH:/usr/local/spark/lib/hbase/*:/usr/local/spark/lib/kafka/*
export SPARK_DIST_CLASSPATH=$(/usr/local/hadoop/bin/hadoop classpath)

儲存該檔案後,退出vim編輯器。然後,就可以啟動spark-shell:

cd /usr/local/spark
./bin/spark-shell

啟動成功後,再次執行命令:

scala> import org.apache.spark.streaming.kafka._
//會顯示下面資訊
import org.apache.spark.streaming.kafka._

在這裡插入圖片描述

說明匯入成功了。這樣,我們就已經準備好了Spark環境,它可以支援kafka相關程式設計了。

編寫Spark程式使用Kafka資料來源

下面,我們就可以進行程式編寫了。請新開啟一個終端,然後,執行命令建立程式碼目錄:

cd /usr/local/spark/mycode
mkdir kafka
cd kafka
mkdir -p src/main/scala
cd src/main/scala
vim KafkaWordProducer.scala

使用vim編輯器新建了KafkaWordProducer.scala,它是產生一系列字串的程式,會產生隨機的整數序列,每個整數被當做一個單詞,提供給KafkaWordCount程式去進行詞頻統計。請在KafkaWordProducer.scala中輸入以下程式碼:

import java.util.HashMap
import org.apache.kafka.clients.producer.{ProducerConfig, KafkaProducer, ProducerRecord}
import org.apache.spark.streaming._
import org.apache.spark.streaming.kafka._
import org.apache.spark.SparkConf

object KafkaWordProducer {
  def main(args: Array[String]) {
    if (args.length < 4) {
      System.err.println("Usage: KafkaWordCountProducer <metadataBrokerList> <topic> " +
        "<messagesPerSec> <wordsPerMessage>")
      System.exit(1)
    }
    val Array(brokers, topic, messagesPerSec, wordsPerMessage) = args
    // Zookeeper connection properties
    val props = new HashMap[String, Object]()
    props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers)
    props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
      "org.apache.kafka.common.serialization.StringSerializer")
    props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
      "org.apache.kafka.common.serialization.StringSerializer")
    val producer = new KafkaProducer[String, String](props)
    // Send some messages
    while(true) {
      (1 to messagesPerSec.toInt).foreach { messageNum =>
        val str = (1 to wordsPerMessage.toInt).map(x => scala.util.Random.nextInt(10).toString)
          .mkString(" ")
        val message = new ProducerRecord[String, String](topic, null, str)
        producer.send(message)
      }
     Thread.sleep(1000)
    }
  }
}

儲存後退出vim編輯器。然後,繼續在當前目錄下建立KafkaWordCount.scala程式碼檔案:

vim KafkaWordCount.scala

KafkaWordCount.scala是用於單詞詞頻統計,它會把KafkaWordProducer傳送過來的單詞進行詞頻統計,程式碼內容如下:

import org.apache.spark._
import org.apache.spark.SparkConf
import org.apache.spark.streaming._
import org.apache.spark.streaming.kafka._
import org.apache.spark.streaming.StreamingContext._
import org.apache.spark.streaming.kafka.KafkaUtils

object KafkaWordCount{
def main(args:Array[String]){
StreamingExamples.setStreamingLogLevels()
val sc = new SparkConf().setAppName("KafkaWordCount").setMaster("local[2]")
val ssc = new StreamingContext(sc,Seconds(10))
ssc.checkpoint("file:///usr/local/spark/mycode/kafka/checkpoint") //設定檢查點,如果存放在HDFS上面,則寫成類似ssc.checkpoint("/user/hadoop/checkpoint")這種形式,但是,要啟動hadoop
val zkQuorum = "localhost:2181" //Zookeeper伺服器地址
val group = "1"  //topic所在的group,可以設定為自己想要的名稱,比如不用1,而是val group = "test-consumer-group" 
val topics = "wordsender"  //topics的名稱          
val numThreads = 1  //每個topic的分割槽數
val topicMap =topics.split(",").map((_,numThreads.toInt)).toMap
val lineMap = KafkaUtils.createStream(ssc,zkQuorum,group,topicMap)
val lines = lineMap.map(_._2)
val words = lines.flatMap(_.split(" "))
val pair = words.map(x => (x,1))
val wordCounts = pair.reduceByKeyAndWindow(_ + _,_ - _,Minutes(2),Seconds(10),2) //這行程式碼的含義在下一節的視窗轉換操作中會有介紹
wordCounts.print
ssc.start
ssc.awaitTermination
}
}        

儲存後退出vim編輯器。然後,繼續在當前目錄下建立StreamingExamples.scala程式碼檔案:

vim StreamingExamples.scala

下面是StreamingExamples.scala的程式碼,用於設定log4j:

import org.apache.spark.Logging
import org.apache.log4j.{Level, Logger}
/** Utility functions for Spark Streaming examples. */
object StreamingExamples extends Logging {
  /** Set reasonable logging levels for streaming if the user has not configured log4j. */
  def setStreamingLogLevels() {
    val log4jInitialized = Logger.getRootLogger.getAllAppenders.hasMoreElements
    if (!log4jInitialized) {
      // We first log something to initialize Spark's default logging, then we override the
      // logging level.
      logInfo("Setting log level to [WARN] for streaming example." +
        " To override add a custom log4j.properties to the classpath.")
      Logger.getRootLogger.setLevel(Level.WARN)
    }
  }
}

這樣,我們在“/usr/local/spark/mycode/kafka/src/main/scala”目錄下,就有了如下三個程式碼檔案:
在這裡插入圖片描述
然後,請執行下面命令:

cd /usr/local/spark/mycode/kafka/
vim simple.sbt

在simple.sbt中輸入以下程式碼:

name := "Simple Project"
version := "1.0"
scalaVersion := "2.10.5"
libraryDependencies += "org.apache.spark" %% "spark-core" % "1.6.2"
libraryDependencies += "org.apache.spark" % "spark-streaming_2.10" % "1.6.2"
libraryDependencies += "org.apache.spark" % "spark-streaming-kafka_2.10" % "1.6.2"  

儲存檔案退出vim編輯器。然後執行下面命令,進行打包編譯:

cd /usr/local/spark/mycode/kafka/
/usr/local/sbt/sbt package

在這裡插入圖片描述
打包成功後,就可以執行程式測試效果了。
首先,請啟動hadoop(有可能你前面採用了ssc.checkpoint(“/user/hadoop/checkpoint”)這種形式,寫入HDFS):

cd /usr/local/hadoop
./sbin/start-all.sh

在這裡插入圖片描述

啟動hadoop成功以後,就可以測試我們剛才生成的詞頻統計程式了。
要注意,我們之前已經啟動了zookeeper服務,啟動了kafka服務,因為我們之前那些終端視窗都沒有關閉,所以,這些服務都在執行。如果你不小心關閉了之前的終端視窗,那就請回到本文前面,再啟動zookeeper服務,啟動kafka服務。

首先,請新開啟一個終端,執行如下命令,執行“KafkaWordProducer”程式,生成一些單詞(是一堆整數形式的單詞):

cd /usr/local/spark
/usr/local/spark/bin/spark-submit --class "KafkaWordProducer" /usr/local/spark/mycode/kafka/target/scala-2.10/simple-project_2.10-1.0.jar localhost:9092 wordsender 3 5

注意,上面命令中,”localhost:9092 wordsender 3 5″是提供給KafkaWordProducer程式的4個輸入引數,第1個引數localhost:9092是Kafka的broker的地址,第2個引數wordsender是topic的名稱,我們在KafkaWordCount.scala程式碼中已經把topic名稱寫死掉,所以,KafkaWordCount程式只能接收名稱為”wordsender”的topic。第3個引數“3”表示每秒傳送3條訊息,第4個引數“5”表示,每條訊息包含5個單詞(實際上就是5個整數)。
執行上面命令後,螢幕上會不斷滾動出現新的單詞,如下:

相關文章