Spark Streaming——使构建可扩展的容错流应用程序变得容易

256 阅读5分钟

1.Spark Streaming的特点

1.使用方便

2.容错能力

3.火花整合

2.Storm 和 Spark Streaming 的区别

3.Spark Streaming初始

1.Spark Streaming是微批处理数据,7*24小时不间断运行

2.Spark Streaming处理数据时,首先启动一个job,这个job使用一个task来一直接收数据,将一段时间内接收到的数据封装在一个batch中,batch没有分布式计算特性,被封装到一个RDD中,这个RDD又被封装在DStream中,生成DStream之后,SparkStreaming启动job处理这个DStream,Spark Streaming底层操作就是DStream,DStream有自己的Transformation算子,懒执行,需要outputOperator类算子触发执行。

接收数据的一段时间可以由我们来控制,叫做batchInterval

假设batchInterval=5s,SparkStreaming处理这个批次数据的时间是3s

假设batchInterval=5s,SparkStreaming处理这个批次数据的时间是8s

4.用Spark Streaming写wordcount

1.添加Spark Streaming依赖

        <!-- https://mvnrepository.com/artifact/org.apache.spark/spark-streaming -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.12</artifactId>
            <version>${spark.version}</version>
            <!--<scope>provided</scope>-->
        </dependency>

【注】:如果添加依赖正常但是导入时还是不行,就把.idea删掉,再Invalidate Caches / Restart,按提示重新设置

2.写代码

package com.jcai.spark.scala

import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext, Time}
object SparkStreamingTest {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("test")
    val ssc = new StreamingContext(conf, Seconds(5))
    val lines = ssc.socketTextStream("ht-1", 9999, StorageLevel.MEMORY_AND_DISK_SER)
    val words: DStream[String] = lines.flatMap(_.split(" "))
    val wordsAndOne: DStream[(String, Int)] = words.map((_, 1))
    val reduce: DStream[(String, Int)] = wordsAndOne.reduceByKey(_ + _)
    reduce.print()
    ssc.start()
    ssc.awaitTermination()//等待被终结
    ssc.stop()//调用不到
  }
}

3.启动一台虚拟机

【注】:虚拟机不自带netcat,需要自行下载编译,编译需要gcc,gcc也需要自行下载,这里附上一篇教程,转自CSDN博客 (blog.csdn.net/z1941563559…)

4.运行

虚拟机:
IDEA:

5.拓展

1.使用foreachRDD拿到DStream中的RDD

package com.jcai.spark.scala

import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext, Time}
object SparkStreamingTest {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("test")
    val ssc = new StreamingContext(conf, Seconds(5))
    val lines = ssc.socketTextStream("ht-1", 9999, StorageLevel.MEMORY_AND_DISK_SER)
    val words: DStream[String] = lines.flatMap(_.split(" "))
    val wordsAndOne: DStream[(String, Int)] = words.map((_, 1))
    val reduce: DStream[(String, Int)] = wordsAndOne.reduceByKey(_ + _)
    //reduce.print()
    reduce.foreachRDD(x=>{
      val RDD: RDD[(String, Int)] = x.map(y=>{
        (y._1+"~",y._2)
      })
      RDD.foreach(x=>{
        println(x)
      })
    })
    ssc.start()
    ssc.awaitTermination()//等待被终结
    ssc.stop()//调用不到
  }
}

虚拟机:

IDEA:

【注】:1)foreachRDD可以拿到DStream中的RDD,对拿到的RDD可以使用RDD的transformation类算子转换,要对拿到的RDD使用action算子触发执行,否则,foreachRDD不会执行

2)

2.updateStateByKey的使用

package com.jcai.spark.scala

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.DStream

object UpdateStateByKeyTest {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("test")
    val ssc = new StreamingContext(conf, Seconds(5))
    ssc.checkpoint("./checkpoint")
    val addFunc = (currValues: Seq[Int], prevValueState: Option[Int]) => {
      //通过Spark内部的reduceByKey按key规约。然后这里传入某key当前批次的Seq/List,再计算当前批次的总和
      val currentCount = currValues.sum
      // 已累加的值
      val previousCount = prevValueState.getOrElse(0)
      // 返回累加后的结果。是一个Option[Int]类型
      Some(currentCount + previousCount)
    }
    val lines = ssc.socketTextStream("ht-1", 9999, StorageLevel.MEMORY_AND_DISK_SER)
    val words: DStream[String] = lines.flatMap(_.split(" "))
    val wordsAndOne: DStream[(String, Int)] = words.map((_, 1))
    val counts = wordsAndOne.updateStateByKey[Int](addFunc)
    counts.print()
    ssc.start()
    ssc.awaitTermination()//等待被终结
    ssc.stop()//调用不到
  }
}

虚拟机:

IDEA:

【注】:1)要设置checkpoint目录

2)如果batchInterval小于10s,那么10s会将内存中的数据写入到磁盘一份,如果batchInterval大于10s,那么就以batchInterval为准,防止频繁的写HDFS

3.reduceByKeyAndWindow的使用

package com.jcai.spark.scala

import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.DStream

object reduceByKeyAndWindowTest {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("test")
    val ssc = new StreamingContext(conf, Seconds(5))
    ssc.sparkContext.setLogLevel("WARN")
    val lines = ssc.socketTextStream("ht-1", 9999, StorageLevel.MEMORY_AND_DISK_SER)
    val words: DStream[String] = lines.flatMap(_.split(" "))
    val wordsAndOne: DStream[(String, Int)] = words.map((_, 1))
    val res: DStream[(String, Int)] = wordsAndOne.reduceByKeyAndWindow((v1:Int, v2:Int) => {v1 + v2},Seconds(15),Seconds(5))
    res.print()

    ssc.start()
    ssc.awaitTermination()//等待被终结
    ssc.stop()//调用不到
  }
}

【注】由于是人工输入,时间长短不一,造成结果参差不齐

4.reduceByKeyAndWindow的优化

   /**
     * 滑动窗口的优化
     */
    /**
     * 窗口操作优化的机制
     * 比如说我们还是每隔五秒查看一下前十五秒数据,我们可以加上新进来的批次,再减去出去的批次,防止任务堆积
     * 用优化机制必须设置checkpoint,不设置会报错
     * 第一个参数是先加上新来的批次
     * 第二个参数是减去出去的批次
     * 第三个参数是窗口长度
     * 第四个参数是滑动间隔
     */
    ssc.checkpoint("./checkpoint1")

    val res1: DStream[(String, Int)] = wordsAndOne.reduceByKeyAndWindow(
      (v1: Int, v2: Int) => {v1 + v2},
      (v1: Int, v2: Int) => {v1 - v2},
      Seconds(15),
      Seconds(15)
    )

    res1.print()

5.Transform的使用

package com.jcai.spark.scala

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

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

    val sparkconf = new SparkConf().setMaster("local[2]").setAppName("test")
    val ssc = new StreamingContext(sparkconf,Seconds(5))

    /**
     * 构建黑名单(要过滤的数据)
     */
    val blacks = List("zs", "ls")  // 一般这条在数据库中,用 read 读进来即可
    val blacksRDD = ssc.sparkContext.parallelize(blacks)//转成RDD
      .map(x => (x, true))
    //将这个元素 x 重新定位为一个新字段  (x,true)
    //(("zs","true"),("ls","true"))

    val lines: ReceiverInputDStream[String] = ssc.socketTextStream("ht-1", 9999)

    //20180808,zs 原来的格式
    //zs,20180808,zs 处理后的格式
    //取index=1的元素,然后在跟上它自身
    val clicklogs = lines.map(x => (x.split(",")(1), x))
      .transform(rdd => {
        //blacksRDD进行map操作后它是RDD格式,此处的lines进行map操作后,它是DStream[U]格式,
        //所以此处,要将DStream和RDD进行联合,就要使用transform算子,
        //通过将RDD-to-RDD函数应用于源DStream的每个RDD来返回新的DStream。
        //这可以用于在DStream上执行任意RDD操作。
        rdd.leftOuterJoin(blacksRDD)
          //进行表的左外连接 leftOuterJoin
          //
          // 端口传进来的数据,经过处理后
          // zs,20180808,zs
          // ls,20180808,ls
          // ww,20180808,ww
          //
          // 黑名单中的数据
          // (("zs","true"),("ls","true"))
          //
          //进行关联后的数据
          // (zs:[<20180808,zs>,<true>])  x
          // (ls:[<20180808,ls>,<true>])  x
          // (ww:[<20180808,ww>,<false>])  ==> tuple 1
          .filter(x => x._2._2.getOrElse(false) != true)
          // 过滤(zs: [<20180808,zs> ,<true>])中,第二个元素的中的第二个元素,判断是否等于true,如果不为true,则返回false,
          // 此处运行后,就只剩下为false的元素了 (ww:[<20180808,ww>,<false>]) ,只有这一条了
          .map(x => x._2._1)
        //取(zs,[<20180808,zs>,<true>])中第二个元素的第一个元素  (tuple的使用)
      })

    clicklogs.print()  //在控制台打印信息   (这块结果应该是有问题的,它只显示最后一个), 应该在 filter或map这块,它是有问题的, 感觉问题在 map, 对元组的掌握,还有待提高

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