超詳細!SparkStreamingがKafkaを統合する方法を詳細に説明する記事!付属のコードを練習できます

ソース|アリス

編集長|キャロル

封图| Visual ChinaのCSDNダウンロード

出品 | CSDN(ID:CSDNnews)

多くの小規模なパートナーが既にSparkStreamingに連絡していると思いますので、理論についてはあまり触れません。今日の内容は、主にSparkStreamingとKafkaの統合に関するチュートリアルを提供することです。

コードはテキストに含まれており、興味のある友達はそれをコピーして試してみることができます!

カフカのレビュー

正式に開始する前に、カフカを確認しましょう。

  • コアコンセプトの図

ブローカー: Kafkaサービスがインストールされているマシンはブローカーです

プロデューサー:ブローカーへのデータの書き込み(プッシュ)を担当するメッセージのプロデューサー

コンシューマー:メッセージのコンシューマー。kafka(プル)からデータをプルする責任があります。コンシューマーの古いバージョンはzkに依存する必要があり、新しいバージョンは必要ありません。

トピック: トピックはデータの分類に相当します。さまざまなトピックがさまざまなビジネスのデータを格納します  –トピック:ビジネスを区別する

複製:コピー、保存されているデータのコピー数(データが失われないようにするため)  –コピー:データのセキュリティ

パーティション:パーティション、物理パーティション、パーティションはファイル、トピックには1〜nのパーティションを含めることができ、各パーティションには独自のコピーがあります- パーティション  :読み取りと書き込みの同時実行

消費者グループ:消費者グループ、トピックは複数の消費者/グループを同時に消費できます。消費者グループに複数の消費者がいる場合、それらはデータを繰り返し消費することはできません  –消費者グループ:消費者消費速度を改善します統合管理

注[1]:トピックは複数のコンシューマまたはグループによってサブスクライブでき、コンシューマ/グループは複数のトピックをサブスクライブすることもできます

注[2]:読み取りデータはリーダーからのみ読み取ることができ、書き込みデータはリーダーにのみ書き込むことができます。フォロワーは、リーダーからのデータを同期してコピーを作成します!

  • 一般的なコマンド

カフカを開始

/export/servers/kafka/bin/kafka-server-start.sh -daemon 

/export/servers/kafka/config/server.properties 

カフカを止めて

/export/servers/kafka/bin/kafka-server-stop.sh 

トピック情報を表示

/export/servers/kafka/bin/kafka-topics.sh --list --zookeeper node01:2181

トピックを作成

/export/servers/kafka/bin/kafka-topics.sh --create --zookeeper node01:2181 --replication-factor 3 --partitions 3 --topic test

トピックに関する情報を表示する

/export/servers/kafka/bin/kafka-topics.sh --describe --zookeeper node01:2181 --topic test

トピックを削除

/export/servers/kafka/bin/kafka-topics.sh --zookeeper node01:2181 --delete --topic test

Start Producer-コンソールのプロデューサーは通常、テストに使用されます

/export/servers/kafka/bin/kafka-console-producer.sh --broker-list node01:9092 --topic spark_kafka

開始コンシューマー–コンソールのコンシューマーは通常、テストに使用されます

/export/servers/kafka/bin/kafka-console-consumer.sh --zookeeper node01:2181 --topic spark_kafka--from-beginning

ボーカーに接続するための消費者のアドレス

/export/servers/kafka/bin/kafka-console-consumer.sh --bootstrap-server node01:9092,node02:9092,node03:9092 --topic spark_kafka --from-beginning 



Kafkaを統合する2つのモードの説明

これはインタビューの質問のホットなトピックでもあります。

開発では、SparkStreamingを使用してリアルタイムでkafkaのデータを読み取ってから処理することがよくあります。spark1.3バージョンの後で、kafkaUtilsはDStreamを作成するための2つのメソッドを提供します。

1.レシーバーの受信方法:

  • KafkaUtils.createDstream(開発では使用されません。理解してください。ただし、インタビューで質問される場合があります)。

  • レシーバーはエグゼキューター内の常駐タスクとして実行され、データを待機しますが、レシーバーは非効率的であり、複数を開いてから手動でデータ(ユニオン)をマージしてから処理する必要があり、非常に面倒です

  • ハングアップしたReceiverのどのマシンがデータを失う可能性があるため、データのセキュリティを確保するためにWAL(事前書き込みログ)を有効にする必要があります。そうしないと、効率が低下します!

  • Receiverメソッドは、zookeeperを介してkafkaキューを接続し、Kafka高レベルAPIを呼び出します。オフセットは、Receiverによって維持されるzookeeperに保存されます。

  • データが失われないようにするために、sparkは使用中にチェックポイントのオフセットも保存し、データの不整合が発生する可能性があります

  • そのため、どの角度から見ても、レシーバーモードは開発での使用に適していません。

2. 直接接続

  • KafkaUtils.createDirectStream(開発で使用、習熟が必要)

  • Directメソッドは、Kafkaパーティションに直接接続してデータを取得する方法で、各パーティションから直接データを読み取ると、並列処理が大幅に向上します。

  • Kafka低レベルAPI(低レベルAPI)を直接呼び出し、オフセットはデフォルトで保存および維持され、Sparkはデフォルトでチェックポイントに維持され、zkとの不整合が解消されます

  • もちろん、手動でメンテナンスして、オフセットをmysqlとredisに保存することもできます

  • そのため、ダイレクトモードをベースとした開発で使用でき、ダイレクトモード+手動操作の特性を活かして、正確に一度だけデータを保証することができます。

要約:

  • 受信機の受信方法

  1. 複数のレシーバーが高効率でデータを受け入れますが、データを失うリスクがあります

  2. ログ(WAL)をオンにするとデータの損失を防ぐことができますが、データを2回書き込むのは非効率的です。

  3. Zookeeperはオフセットを維持し、データを繰り返し消費する可能性があります。

  4. 高レベルAPIを使用する

  • 直接接続

  1. レシーバーを使用せずにKafkaパーティションで直接データを読み取る

  2. ロギング(WAL)メカニズムを使用しない

  3. Sparkはオフセット自体を維持します

  4. 低レベルAPIを使用する

拡張子:メッセージのセマンティクスについて

開発に統合されているSparkStreamingとkafkaの2つのバージョン:0.8と0.10+

0.8バージョンには、ReceiverモードとDirectモードがあります(ただし、0.8バージョンには本番環境の問題が多く、0.8バージョンはSpark2.3以降ではサポートされていません)。

0.10以降、ダイレクトモードのみが保持され(Reivererモードは本番環境には適していません)、0.10バージョンのAPIが変更されました(より強力)

結論:

0.10バージョンではダイレクトモードを使用して直接研究開発を行っていますが、ReceiverとDirectの違いについてのインタビューはそれに答えることができるはずです。

spark-streaming-kafka-0-8(理解)

1.Receiver

KafkaUtils.createDstreamはレシーバーを使用してデータを受信し、Kafkaの高レベルコンシューマーAPIを使用します。オフセットはレシーバーによってzkで維持され、すべてのレシーバーによって受信されたデータはSparkエグゼキューターに保存され、次にSparkを介して保存されます。ストリーミングは、デフォルトで失われるこれらのデータを処理するジョブを開始します。WALログを有効にできます。受信したデータを同期して、HDFSなどの分散ファイルシステムに保存します。エラーが発生した場合にデータを回復できることを確認してください。この方法をWALメカニズムと組み合わせると、データ損失ゼロの高い信頼性を確保できますが、WALの効率は低く、データが1回だけ処理され、2回処理される可能性はありません。SparkとZooKeeperが同期していない可能性があるためです。

(現時点では、この種の統合は推奨されていません。)

  • 準備

1)Zookeeperクラスターを開始します

zkServer.sh start

2)kafkaクラスターを開始します

kafka-server-start.sh  /export/servers/kafka/config/server.properties

3.トピックを作成する

kafka-topics.sh --create --zookeeper node01:2181 --replication-factor 1 --partitions 3 --topic spark_kafka

4.シェルコマンドを使用してトピックにメッセージを送信する

kafka-console-producer.sh --broker-list node01:9092 --topic  spark_kafka

5. kafkaのpom依存関係を追加する

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming-kafka-0-8_2.11</artifactId>
    <version>2.2.0</version>
</dependency>
  • API

レシーバーを介してkafkaのトピックデータを取得します。より多くのレシーバーを実行して、kafakトピックのデータを並行して読み取ることができます。

 val receiverDStream: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => {
      val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
      stream
    })

WALが有効な場合(spark.streaming.receiver.writeAheadLog.enable = true)、ストレージレベルを設定できます(デフォルトのStorageLevel.MEMORY_AND_DISK_SER_2)。

コードデモ

import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.immutable

object SparkKafka {
  def main(args: Array[String]): Unit = {
    //1.创建StreamingContext
    val config: SparkConf = 
new SparkConf().setAppName("SparkStream").setMaster("local[*]")
      .set("spark.streaming.receiver.writeAheadLog.enable", "true")
//开启WAL预写日志,保证数据源端可靠性
    val sc = new SparkContext(config)
    sc.setLogLevel("WARN")
    val ssc = new StreamingContext(sc,Seconds(5))
    ssc.checkpoint("./kafka")
//==============================================
    //2.准备配置参数
    val zkQuorum = "node01:2181,node02:2181,node03:2181"
    val groupId = "spark"
    val topics = Map("spark_kafka" -> 2)//2表示每一个topic对应分区都采用2个线程去消费,
//ssc的rdd分区和kafka的topic分区不一样,增加消费线程数,并不增加spark的并行处理数据数量
    //3.通过receiver接收器获取kafka中topic数据,可以并行运行更多的接收器读取kafak topic中的数据,这里为3个
    val receiverDStream: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => {
      val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics)
      stream
    })
    //4.使用union方法,将所有receiver接受器产生的Dstream进行合并
    val allDStream: DStream[(String, String)] = ssc.union(receiverDStream)
    //5.获取topic的数据(String, String) 第1个String表示topic的名称,第2个String表示topic的数据
    val data: DStream[String] = allDStream.map(_._2)
//==============================================
    //6.WordCount
    val words: DStream[String] = data.flatMap(_.split(" "))
    val wordAndOne: DStream[(String, Int)] = words.map((_, 1))
    val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }
}

2.直接

Directメソッドは、Kafkaのトピックで対応するパーティションから最新のオフセットを定期的に照会し、オフセット範囲に従って各バッチのデータを処理します。Sparkは、KafkaシンプルコンシューマーAPIを呼び出して特定の範囲のデータを読み取ります。

  • 直接の欠点は、飼育係に基づくカフカ監視ツールを使用できないことです

  • ダイレクトにはレシーバーに比べていくつかの利点があります。

  1. 並列処理を簡素化

    複数のkafka入力ストリームを作成してからそれらを結合する必要はありません。SparkStreamingは、Kafkaパーティションと同じ数のRDDパーティションを作成し、Kafkaから並列にデータを読み取ります。SparkのRDDパーティションの数とKafkaのパーティションデータは1対1の関係です。

  2. 効率的 

    Receiverで達成されるデータ損失ゼロは、データをWALに事前に保存することです。データは1回コピーされるため、データが2回コピーされ、最初はkafkaによってコピーされ、もう1回はWALに書き込まれます。DirectはWALを使用してこの問題を解消していません。

  3. 1回限りのセマンティクス

    レシーバーは、kafka高レベルAPIを介してkafkaデータを読み取り、オフセットをzookeeperに書き込みます。この方法では、データがWALに保存され、データが失われないようにすることができますが、sparkStreamingとZKに保存されているオフセットが矛盾していることが原因である可能性がありますその結果、データは何度も消費されました。

        DirectのExactly-once-semantics(EOS)は低レベルのカフカAPIを実装しており、オフセットはチェックポイントのsscによってのみ保存されるため、zkオフセットとsscオフセット間の不整合の問題が解消されます。

  • API

KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics)

コードデモ

import kafka.serializer.StringDecoder
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}


object SparkKafka2 {
  def main(args: Array[String]): Unit = {
    //1.创建StreamingContext
    val config: SparkConf = 
new SparkConf().setAppName("SparkStream").setMaster("local[*]")
    val sc = new SparkContext(config)
    sc.setLogLevel("WARN")
    val ssc = new StreamingContext(sc,Seconds(5))
    ssc.checkpoint("./kafka")
    //==============================================
    //2.准备配置参数
    val kafkaParams = Map("metadata.broker.list" -> "node01:9092,node02:9092,node03:9092", "group.id" -> "spark")
    val topics = Set("spark_kafka")
    val allDStream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics)
    //3.获取topic的数据
    val data: DStream[String] = allDStream.map(_._2)
    //==============================================
    //WordCount
    val words: DStream[String] = data.flatMap(_.split(" "))
    val wordAndOne: DStream[(String, Int)] = words.map((_, 1))
    val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }
}


spark-streaming-kafka-0-10

  • 解説

spark-streaming-kafka-0-10バージョンでは、APIにいくつかの変更が加えられ、操作がより柔軟になり、開発で使用されます

  • pom.xml

<!--<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming-kafka-0-8_2.11</artifactId>
    <version>${spark.version}</version>
</dependency>-->
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
    <version>${spark.version}</version>
</dependency>
  • API:

http://spark.apache.org/docs/latest/streaming-kafka-0-10-integration.html

  • トピックを作成

/export/servers/kafka/bin/kafka-topics.sh --create --zookeeper node01:2181 --replication-factor 3 --partitions 3 --topic spark_kafka

  • プロデューサーを開始

/export/servers/kafka/bin/kafka-console-producer.sh --broker-list node01:9092,node01:9092,node01:9092 --topic spark_kafka

  • コードデモ

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object SparkKafkaDemo {
  def main(args: Array[String]): Unit = {
    //1.创建StreamingContext
    //spark.master should be set as local[n], n > 1
    val conf = new SparkConf().setAppName("wc").setMaster("local[*]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")
    val ssc = new StreamingContext(sc,Seconds(5))//5表示5秒中对数据进行切分形成一个RDD
    //准备连接Kafka的参数
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "node01:9092,node02:9092,node03:9092",
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "SparkKafkaDemo",
      //earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
      //latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
      //none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
      //这里配置latest自动重置偏移量为最新的偏移量,即如果有偏移量从偏移量位置开始消费,没有偏移量从新来的数据开始消费
      "auto.offset.reset" -> "latest",
      //false表示关闭自动提交.由spark帮你提交到Checkpoint或程序员手动维护
      "enable.auto.commit" -> (false: java.lang.Boolean)
    )
    val topics = Array("spark_kafka")
    //2.使用KafkaUtil连接Kafak获取数据
    val recordDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](ssc,
      LocationStrategies.PreferConsistent,//位置策略,源码强烈推荐使用该策略,会让Spark的Executor和Kafka的Broker均匀对应
      ConsumerStrategies.Subscribe[String, String](topics, kafkaParams))//消费策略,源码强烈推荐使用该策略
    //3.获取VALUE数据
    val lineDStream: DStream[String] = recordDStream.map(_.value())//_指的是ConsumerRecord
    val wrodDStream: DStream[String] = lineDStream.flatMap(_.split(" ")) //_指的是发过来的value,即一行数据
    val wordAndOneDStream: DStream[(String, Int)] = wrodDStream.map((_,1))
    val result: DStream[(String, Int)] = wordAndOneDStream.reduceByKey(_+_)
    result.print()
    ssc.start()//开启
    ssc.awaitTermination()//等待优雅停止
  }
}

この記事では、主にSparkStreamingをKafkaと統合するプロセスについて説明し、Kafkaの基本的な知識を復習します。役立つ場合は、「見て」ください。

この記事は、元のリンクであるCSDNブログの作成者によって最初に公開されました。

https://blog.csdn.net/weixin_44318830/article/details/105612516

【終わり】

よりエキサイティングな推奨事項

30年間のオープンソースの興奮:フリーコミュニティから数十億ドル規模の企業へ

☞AIの最大の成果の1つを理解する:畳み​​込みニューラルネットワークの制限

GitHubスター10,000以上、ApacheのトッププロジェクトShardingSphereのオープンソースロード

香港科学技術大学の学者鄭廣定が未来について尋ね、AIの最新のアプリケーションと実践を公開する

大きなプロモーションの下でのインテリジェントなO&Mチャレンジ:アリは「ダブル11キャットナイト」にどのように対抗できますか?

イーサネットスクエア2.0カストディゲームとMPCを実装

☞9 つのMySQLインタビューの質問を非常に注意深く書きました。

あなたが注文するすべての「ウォッチング」、私はそれを真剣に受け止めます

1984年の元の記事の公開 40,000以上の賞賛 18.44万回の表示

おすすめ

転載: blog.csdn.net/csdnnews/article/details/105697457