Spark Learning Journey (4) ストリーミングの活用

Spark Streaming は Apache Storm に似ており、ストリーミング データ処理に使用されます。いわゆるストリーミング処理とは、実はリアルタイムデータのことで、これまでのSparkはオフラインデータ、つまりデータファイルを直接処理していましたが、ストリーミングは常にデータを検出して、出てきたデータを一つずつ処理していきます。公式ドキュメントによると、Spark Streaming は高スループットと強力な耐障害性という特徴を持っています。Spark Streaming は、Kafka、Flume、Twitter、ZeroMQ、単純な TCP ソケットなど、多くのデータ入力ソースをサポートしています。データが入力されると、マップ、リデュース、結合、ウィンドウなどの Spark の高度に抽象化されたプリミティブを操作に使用できます。結果は、HDFS、データベースなど、さまざまな場所に保存することもできます。さらに、Spark Streaming は MLlib (機械学習) および Graphx と完全に統合することもできます。

Spark Streaming は、DStream と呼ばれる、離散化されたストリームを抽象表現として使用します。DStream は、時間の経過とともに受信される一連のデータです。内部的には、各時間間隔で受信したデータが RDD として存在し、DStream はこれらの RDD から構成されるシーケンスです (したがって、「離散化」と呼ばれます)。

DStream は、Flume、Kafka、HDFS などのさまざまな入力ソースから作成できます。作成された DStream は、新しい DStream を生成する変換操作 (transformation) と、外部システムにデータを書き込むことができる出力操作 (output 操作) の 2 つの操作をサポートします。DStream は、RDD でサポートされているものと同様の多くの操作をサポートし、スライディング ウィンドウなどの新しい時間関連の操作も追加します。

ストリームの簡単な使い方

まずメッセージを送信するコードを作成します

// 主要按照套接字发送数据
object CreateData {
    
    
  def main(args: Array[String]): Unit = {
    
    
    // 通过套接字发送数据
    val listener = new ServerSocket(9888)
    while(true){
    
    
      val socket = listener.accept()
      new Thread(){
    
    
        override def run() = {
    
    
          println("Got client connected from :"+ socket.getInetAddress)
          val out = new PrintWriter(socket.getOutputStream,true)
          while(true){
    
    
            Thread.sleep(1000)
            val context1 = "张三~李四~王五~张三"
            out.write(context1 + '\n')
            out.flush()
          }
          socket.close()
        }
      }.start()
    }
  }
}

次に、パラメータを受け取るストリームを作成します

ストリーミング コードは受信して処理します。

ここで注意してください: 各レシーバーは Spark エグゼキューター プログラムの長時間実行タスクとして実行されるため、アプリケーションに割り当てられた CPU コアを占有します。さらに、データを処理するために利用可能な CPU コアが必要です。つまり、複数のレシーバーを実行する場合は、少なくともレシーバーの数に計算を完了するために必要なコアの数を加えたものと同じ数のコアが必要です。

ストリーミング コンピューティング アプリケーションで 10 個の受信機を実行したい場合、アプリケーションに少なくとも 11 個の CPU コアを割り当てる必要があります。

簡単に言うと、local や local[1] は使用しないのが最善です。

object StreamingDemo {
    
    

  def main(args: Array[String]): Unit = {
    
    
    // Seconds 数据间隔的时间
    val ssc = new StreamingContext(new SparkConf()
      .setAppName("stream")
      .setMaster("local[2]"), Seconds(10))

    ssc.sparkContext.setLogLevel("WARN")

    // 设置检查点
    ssc.checkpoint("./check")

    val data = ssc.socketTextStream("localhost",9888)

    data.print()

    ssc.start()
    ssc.awaitTermination()
  }

}

上記のコードでは、Streaming 自体がストリーム処理を一連の連続バッチ処理に変換する「マイクロバッチ」アーキテクチャを採用していることがわかります。

一般的に使用されるアルゴリズム

Streamでよく使われるメソッドは大きく変換メソッドと出力メソッドに分けられ、変換メソッドはステートレス変換とステートフル変換に分かれます。

一般的な変換方法

一般的に使用されるアルゴリズムは RDD のものと似ていますが、唯一の違いは、元の RDD タイプが DStream に置き換えられることです。

メソッド名 具体作用
マップ(機能) ソース DStream の各要素を関数 func に渡して、新しい DStream を取得します。
flatMap(関数) マップと似ていますが、各入力項目は 0 個以上の項目にマッピングできます。
フィルター(関数) ソース DStream 内で関数 func が true と評価されるレコードを新しい DStream として選択します
再パーティション(パーティション数) 作成するパーティションの数を増減して、この DStream の並列処理レベルを変更します。
ユニオン(otherStream) ソース DStream を他の DStream と結合して新しい DStream を取得する
カウント() ソース DStream 内の各 RDD に含まれる要素の数を数えて、単一要素 RDD の新しい DStream を取得します。
リデュース(機能) 関数 func (2 つのパラメーターと 1 つの出力) を使用して、ソース DStream の各 RDD 要素を統合し、単一要素 RDD の DStream を取得します。
reduceByKey(func, [numTasks]) (K, V) ペアの DStream でこの関数を呼び出すと、同じ (K, V) ペアの新しい DStream が返されますが、新しい DStream 内の対応する V は、reduce 関数を使用して統合されます。: デフォルトでは、この操作では Spark のデフォルトの並列タスク数 (ローカル モードでは 2、クラスター モードでは数は設定パラメーターspark.default.Parallelism によって異なります) が使用されます。オプションのパラメーター numTaska を渡して、別のタスク数を設定することもできます。
join(otherStream, [numTasks]) 2 つの DStream はそれぞれ (K, V) と (K, W) のペアであり、(K, (V, W)) ペアの新しい DStream が返されます。
cogroup(otherStream, [numTasks]) 2 つの DStream はそれぞれ (K, V) と (K, W) のペアであり、新しい DStream の (K, (Seq[V], Seq[W])] ペアを返します。

ステートフル遷移 UpdateStateByKey

ストリーミングでは、すべてのバッチに出現する単語の数をカウントするなど、バッチ全体でデータを処理する必要がある場合があります。updateStateByKey() は、キーと値のペアの形式で DStream の状態変数へのアクセスを提供します。(キー、イベント) ペアで構成される DStream が与えられ、新しいイベントに従って各キーに対応する状態を更新する方法を指定する関数を渡すと、(キー、状態) が正しい内部データを持つ新しい DStream を構築できます。

このメソッドを単語統計の例、特定のコードで使用します。

def main(args: Array[String]): Unit = {
    
    

    // 创建对象
    val ssc = new StreamingContext(
      new SparkConf()
        .setAppName("streaming")
        .setMaster("local[2]"),Seconds(5)
    )

    ssc.checkpoint("./check")

    val data = ssc
      .socketTextStream("127.0.0.1",9888)

    // 计算每个人的数量
    val names = data.flatMap(
      line => {
    
    
        val name = line.split("~")
        name
      }
    ).map((_,1))

    val result = names.updateStateByKey[Int](
      (values:Seq[Int],state:Option[Int]) => {
    
    
        // (当前key值的集合,之前累加的状态) => { 返回值 }
        // 获得之前状态中的数据
        var count = state.getOrElse(0)
        // 遍历当前批次中的数据
        for(value <- values){
    
    
          // 累加
          count += value
        }
        // Some:Option子类,表示有值
        Some(count)
      }
    )

    result.print()

    ssc.start()
    ssc.awaitTermination()
  }
データベースに保存されたストリーミング

データベース アクセスは JDBC を使用して処理する必要があります。次に、単語頻度統計を例としてデータベースに保存する方法を示します。データベースストレージでは最初にテーブルを作成する必要があります

CREATE TABLE `word` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `key` varchar(255) DEFAULT NULL,
  `value` int(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;

次に、JDBC ストレージを実行します

def main(args: Array[String]): Unit = {
    
    
    // StreamingContext(spark配置,时间间隔)
    val ssc = new StreamingContext(
      new SparkConf().setAppName("streaming")
        .setMaster("local[2]"),Seconds(3)
    )
    ssc.sparkContext.setLogLevel("ERROR")

    // 设置检查点 streaming是24*7
    ssc.checkpoint("./check_port")

    println("这里是Streaming")

    val data = ssc.socketTextStream("127.0.0.1",9888)

    val mapData = data
      .flatMap(_.split("~"))
      .map((_,1)) // (张三,1) , (李四,1)

    // state 状态,此时数据
    // updateStateByKey[返回值中数据的类型]
    val result = mapData.updateStateByKey[Int](
      // values => 此批次中相同key的值集合
      // key是张三的数据,放在values (1,1,1...)
      // state => 之前这个key计算的数据(状态) (张三,12)
      (values:Seq[Int],state:Option[Int]) => {
    
    
        var count = state.getOrElse(0)
        for(v <- values){
    
    
          count += v
        }
        Some(count)
      }
    )

    result.print()
    result.foreachRDD(
      item => {
    
    
        if(!item.isEmpty()){
    
     // 判断RDD是否为空
          item.foreach{
    
     // 遍历RDD中数据,分别取出存入mysql
            case(key,count) => {
    
    
              // 获得连接
              val connection = getConnection()
              /* 定义SQL,注意word数据库需要提前创建 */
              val sql = "insert into `word` (`key`,`value`) values (?,?)"
              /* 创建prepareStatement对象,用于执行SQL */
              val state = connection.prepareStatement(sql)
              /* 占位符(?的位置)插入数据 */
              state.setString(1,key)
              state.setInt(2,count)
              /* 执行SQL */
              state.execute()
            }
          }
        }
      }
    )

    ssc.start()
    ssc.awaitTermination()

  }

  def getConnection() : Connection = {
    
    
    Class.forName("com.mysql.jdbc.Driver")
    DriverManager.getConnection(
      "jdbc:mysql://127.0.0.1:3306/ssm",
      "root","root")
  }
ストリーミングウィンドウ方式

データ ストリームは時間順に基づいてバッチで処理されるため、別の概念である時間ウィンドウ (後でウィンドウと呼ばれます) が導入されます。このウィンドウは、バッチ内のすべてのデータに似ています。

各ウィンドウは、ウィンドウの長さとスライディング間隔によって定義され、どちらも StreamContext のバッチ間隔の整数倍である必要があります。ウィンドウの長さは、時間の長さ内に受け入れられる RDD を取得することを指し、スライディング間隔は秒数を指します。データを取りに行く

これは単純なウィンドウの例です。データプロデューサーは依然として上記のコードです


def main(args: Array[String]): Unit = {
    
    

    // 创建对象
    val ssc = new StreamingContext(
      new SparkConf()
        .setAppName("streaming")
        .setMaster("local[2]"),Seconds(5)
    )

    ssc.sparkContext.setLogLevel("ERROR")
    ssc.checkpoint("./stream_checkpoint")

    val data = ssc
      .socketTextStream("127.0.0.1",9888)

    // 创建一个15秒窗口时间,5秒间隔时间的窗口
    val winData = data.window(Seconds(15),Seconds(5))
    winData.print()

    ssc.start()
    ssc.awaitTermination()
  }

一般的な方法

メソッド名 メソッドの説明
window(ウィンドウの長さ、間隔) ソース DStream のウィンドウ化されたバッチの計算に基づいて、新しい DStream を返します。
countByWindow(ウィンドウの長さ、間隔) ストリーム内の要素のスライディング ウィンドウ数を返します。
reduceByWindow(func, windowLength, slideInterval) カスタム関数を使用してスライディング レンジ ストリームの要素を組み合わせて、新しい単一要素ストリームを作成します。
reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]) (K, V) ペアの DStream でこの関数を呼び出すと、(K, V) ペアの新しい DStream が返されます。ここでは、バッチ データに対して Reduce 関数を使用して各キーの値を統合します。引き違い窓です。**注意:** デフォルトでは、この操作は Spark のデフォルトの並列タスク数 (ローカルでは 2) を使用し、グループ化はクラスター モードの構成プロパティ (spark.default.Parallelism) に従って行われます。オプションのパラメーター numTasks を設定することで、異なる数のタスクを設定できます。

おすすめ

転載: blog.csdn.net/lihao1107156171/article/details/115587995