day74 Spark - streaming


I know, i know
地球另一端有你陪我




一、Spark - streaming

在这里插入图片描述
微批处理,一定时间内将该段时间产生的数据进行批处理,是一种近似的实时处理

1、WordCount

package streaming

import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object Demo1WordCount {

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

    /**
      * 创建spark 环境
      * local 模式需要至少两个线程
      * 后台会需要一个线程接收socket
      * 另一个处理信息
      *
      */

    val conf: SparkConf = new SparkConf()
      .setAppName("Demo1WordCount")
      .setMaster("local[2]")

    val sc: SparkContext
    = new SparkContext(conf)

    /**
      * 创建spark streaming环境
      * 指定每隔多久计算一次
      */

    val ssc: StreamingContext
    = new StreamingContext(sc, Durations.seconds(5))


    /**
      * 读取数据
      * yum install nc
      * 读取socket  数据
      * nc -lk  8888
      * 用于测试
      */

    val linesDS: ReceiverInputDStream[String] 
    		= ssc.socketTextStream("master", 8888)

    /**
      * 处理数据
      */

    linesDS
      .flatMap(_.split(","))
      .map((_, 1))
      .reduceByKey(_ + _)
      //DS 中使用 print 打印
      .print()

    ssc.start()
    ssc.awaitTermination()
    ssc.stop()

  }
}

2、UpdateStateByKey

带状态算子(名字品味真的很差)
总之就是可以保留之前批次留下来的数据

package streaming

import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object Demo2UpdateStateByKey {

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

    /**
      * 创建spark 环境
      */

    val conf = new SparkConf()
      .setAppName("Demo2UpdateStateByKey")
      .setMaster("local[2]")

    val sc: SparkContext = new SparkContext(conf)

    /**
      * 创建spark streaming环境
      * 指定每隔多久计算一次
      */

    val ssc: StreamingContext
    = new StreamingContext(sc, Durations.seconds(5))

    // 有状态算子需要设置checkpoint
    // 用于保存之前的计算状态
    ssc.checkpoint("Spark/data/checkpoint")

    // 读取数据
    val linesDS = ssc.socketTextStream("master", 8888)

    val wordsDS = linesDS.flatMap(_.split(","))

    val kvDS: DStream[(String, Int)] = wordsDS.map((_, 1))


    // 有状态算子,可以预读被持久化的,之前滑动窗口的数据
    /**
      * 更新函数: 使用当前batch的数据去更新之前的计算结果,返回一个新的结果
      *
      * seq: 当前batch一个单词所有的value
      * opt: 之前一个单词的计算结果(状态),  
      * 使用Option的原因是如果是第一次计算之前没有结果,那就是NOne
      */

    val updateFun = (seq: Seq[Int], opt: Option[Int]) => {

      //计算当前batch单词的数量
      val currCount: Int = seq.sum

      //获取之前的计算结果
      val oldCount: Int = opt.getOrElse(0)

      // 和即是最新的数量
      val count: Int = currCount + oldCount

      // 需要返回一个option
      Option(count)
    }

    kvDS
      .updateStateByKey(updateFun)
      .print()

    ssc.start()
    ssc.awaitTermination()
    ssc.stop()
  }
}

3、foreachRDD

package streaming

import org.apache.spark.SparkContext
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Durations, StreamingContext}

object Demo3ToRDD {

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

    val spark = SparkSession
      .builder()
      .master("local[2]")
      .appName("Demo3ToRDD")
      .getOrCreate()

    import org.apache.spark.sql.functions._
    import spark.implicits._


    val sc: SparkContext = spark.sparkContext

    val ssc
    = new StreamingContext(sc, Durations.seconds(5))

    val linesDS = ssc.socketTextStream("master", 8888)

    /**
      * foreachRDD: 将DStream转换成rdd
      * 无返回值,等于是将每一个批处理
      * 单独拿出来处理
      */
    linesDS.foreachRDD(rdd => {

      rdd
        .flatMap(_.split(","))
        .map((_, 1))
        .reduceByKey(_ + _)


      // 将rdd再转换成DataFrame
      // 预先设置列名
      val df: DataFrame = rdd.toDF("line")

      // 注册成视图
      df.createOrReplaceTempView("table")

      df
        .select(explode(split($"line", ",")) as "words")
        .groupBy($"words")
        .agg(count($"words") as "cnt")
        .show()


    })

    // transform : 将DStream转换成rdd , 
    // 在transform里面编写rdd的代码,写完之后需要返回一个新的rdd

    val KVDS: DStream[(String, Int)] = linesDS.transform(rdd => {

      val kv = rdd
        .flatMap(_.split(","))
        .map((_, 1))
        .reduceByKey(_ + _)
      kv
    })

    KVDS
      .reduceByKey(_ + _)
      .print()


    ssc.start()
    ssc.awaitTermination()
    ssc.stop()
  }
}

4、模拟带状态算子

很多暗坑需要注意,确实难,这个东西

package streaming

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.SparkContext
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
import org.apache.spark.streaming.{Durations, StreamingContext}

object Demo4StreamOnRDD {

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

    val spark = SparkSession
      .builder()
      .appName("Demo4StreamOnRDD")
      .master("local[2]")
      .config("spark.sql.shuffle.partitions", 1)
      .getOrCreate()

    import org.apache.spark.sql.functions._
    import spark.implicits._

    val sc: SparkContext = spark.sparkContext

    val ssc: StreamingContext
    = new StreamingContext(sc, Durations.seconds(5))


    val linesDS
    = ssc.socketTextStream("master", 8888)


    linesDS.foreachRDD(rdd => {

      val df: DataFrame = rdd.toDF("word")

      val countDF: DataFrame = df
        .select(explode(split($"word", ",")) as "word")
        .groupBy($"word")
        .agg(count($"word") as "cnt")

      val path = "Spark/data/stream_count"


      val conf = new Configuration()
      val fs: FileSystem = FileSystem.get(conf)

      /**
        * 如果之前有结果,将当前batch的结果和之前的结果合并再保存
        * 如果之前没有结果,直接保存当前的结果
        *
        * 此处是一个暗坑,如果使用一个目录进行读写
        * 会导致在一次RDD中既有读,也有写,造成 socket 冲突,
        * 为此,需要再开一个临时目录,进行缓冲
        */

      if (fs.exists(new Path("Spark/data/stream_count"))) {

        /**
          * 读取上一次计算的结果
          */

        val beforDF: DataFrame = spark
          .read
          .format("csv")
          .schema("word String, cnt Long")
          .load("Spark/data/stream_count")


        // 合并这次读取的结果和上一次读取的结果
        val allDF: DataFrame = beforDF
          .union(countDF)
          .groupBy($"word")
          .agg(sum($"cnt") as "cnt")

        // ;保存数据
        allDF
          .write
          .mode(SaveMode.Overwrite)
          .format("csv")
          // 这里存到临时文件
          .save("Spark/data/stream_count_temp")


        // 这里是关键,对读写进行错开
        // 先删除原文件,再将临时文件上位
        fs.delete(new Path("Spark/data/stream_count"), true)
        fs.rename(new Path("Spark/data/stream_count_temp"), new Path("Spark/data/stream_count"))

      } else {

        // 如果没有目录,说明是第一次
        // 直接给他存起来
        countDF
          .write
          .mode(SaveMode.Overwrite)
          .format("csv")
          .save("Spark/data/stream_count")

      }
    })

    ssc.start()
    ssc.awaitTermination()
    ssc.stop()
  }
}

5、滑动窗口

在这里插入图片描述

package streaming

import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

object Demo5Window {

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

    val conf = new SparkConf()
      .setAppName("Demo5Window")
      .setMaster("local[2]")

    val sc: SparkContext = new SparkContext(conf)

    val ssc = new StreamingContext(sc, Durations.seconds(5))

    ssc.checkpoint("Spark/data/checkpoint")

    val linesDS: ReceiverInputDStream[String] 
    		= ssc.socketTextStream("master", 8888)

    val kvDS = linesDS
      .flatMap(_.split(","))
      .map((_, 1))

    /**
      * 统计最近15秒单词的数量,每隔5秒统计一次
      *
      *
      * 窗口大小和滑动时间必须是batch时间的整数倍
      *
      */

    /*val countDS: DStream[(String, Int)]
    	 = kvDS.reduceByKeyAndWindow(
      (x: Int, y: Int) => x + y, //聚合函数
      Durations.seconds(15), //窗口大小
      Durations.seconds(5) //滑动时间
    )*/

    /**
      * 如果窗口存在交叉的清空,会重复计算数据
      * 所有可以对窗口计算进行优化,
      *
      */


    val cuntDS: DStream[(String, Int)] = kvDS.reduceByKeyAndWindow(

      (x: Int, y: Int) => x + y, //聚合函数
      // 这句是滑动窗口的一句优化
      // 邻近窗口之间只是几个批量的差别
      // 因此可以直接在上一个批量的结果中修改
      (i: Int, j: Int) => i - j, //减去多余数据的函数
      Durations.seconds(15), //窗口大小
      Durations.seconds(5) //滑动时间
    )

    cuntDS
      .filter(_._2 != 0)
      .print()


    ssc.start()
    ssc.awaitTermination()
    ssc.stop()
  }
}

6、稽查布控

package streaming

import java.sql.{DriverManager, PreparedStatement, ResultSet}

import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable.ListBuffer

object Demo6JCBC {

  // 稽查布控

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

    val conf = new SparkConf()
      .setMaster("local[2]")
      .setAppName("Demo6JC")


    val sc: SparkContext = new SparkContext(conf)

    val ssc: StreamingContext
    = new StreamingContext(sc, Durations.seconds(5))


    val linesDS = ssc.socketTextStream("master", 8888)

    /**
      * transform, foreachRDD  
      * 的外面属于Driver端,只执行一次
      * 
      * transform, foreachRDD  
      * 的内部算子的外部属于Driver端,买一个batch都会执行一次
      * 
      * 算子内属于Executor端,每一条数据执行一次
      */

    val filterDS = linesDS.transform(rdd => {

      Class.forName("com.mysql.jdbc.Driver")

      val conn 
      		= DriverManager.getConnection("jdbc:mysql://master:3306/student", "root", "123456")

      val ps: PreparedStatement 
      		= conn.prepareStatement("select mdn from list")

      val rs: ResultSet = ps.executeQuery()

      val list = new ListBuffer[String]
      while (rs.next()) {
        val word = rs.getString("mdn")
        list += word
      }

      conn.close()

      println(s"布控列表:$list")

      // 广播变量
      val brodList = sc.broadcast(list)

      val filterRDD = rdd.filter(line => {
        val mdn = line.split(",")(0)

        brodList.value.contains(mdn)
      })


      brodList.unpersist()

      filterRDD
    })
    filterDS

    filterDS.print()


    ssc.start()
    ssc.awaitTermination()
    ssc.stop()
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值