Spark-Streaming流式处理学习与快速入门指南

1 Spark Streaming是什么

Spark Streaming 是 Spark 核心 API 的扩展, 用于构建弹性, 高吞吐量, 容错的在线数据流的流式处理程序. 总之一句话: Spark Streaming 用于流式数据的处理
在这里插入图片描述
在 Spark Streaming 中,处理数据的单位是一批而不是单条,而数据采集却是逐条进行的,因此 Spark Streaming 系统需要设置间隔使得数据汇总到一定的量后再一并操作,这个间隔就是批处理间隔。批处理间隔是 Spark Streaming 的核心概念和关键参数,它决定了 Spark Streaming 提交作业的频率和数据处理的延迟,同时也影响着数据处理的吞吐量和性能。
在这里插入图片描述

2 DStream

Discretized Stream(DStream) 是 Spark Streaming 提供的基本抽象, 表示持续性的数据流, 可以来自输入数据, 也可以是其他的 DStream 转换得到. 在内部, 一个 DSteam 用连续的一系列的 RDD 来表示. 在 DStream 中的每个 RDD 包含一个确定时间段的数据

在这里插入图片描述

2.1 wordcount案例

使用 netcat 工具向 9999 端口不断的发送数据,通过 Spark Streaming 读取端口数据并统计不同单词出现的次数

2.2 添加依赖

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_2.11</artifactId>
    <version>2.1.1</version>
</dependency>

2.3 代码如下

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

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("StreamingWordCount").setMaster("local[2]")
    // 1. 创建SparkStreaming的入口对象: StreamingContext  参数2: 表示事件间隔   内部会创建 SparkContext
    val ssc = new StreamingContext(conf, Seconds(5))
    // 2. 创建一个DStream
    val lines: ReceiverInputDStream[String] = ssc.socketTextStream("192.168.199.101", 9999)
    // 3.切分单词
    val words: DStream[String] = lines.flatMap(_.split(" "))
    // 4.单词组成元组
    val wordAndOne: DStream[(String, Int)] = words.map(x => (x, 1))
    // 5.统计单词个数
    val count: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    // 6.显示
    count.print()
    // 7.开始接收数据
    ssc.start()
    //8. 等待计算结束(要么手动退出,要么出现异常)才退出主程序
    ssc.awaitTermination()
  }

}

2.4 在linux服务器上启动netcat

nc -lk 9999

2.5 运行streamning

可以看到会每隔5秒钟去采集数据并展示出来
在这里插入图片描述
在这里插入图片描述

2.6执行流程

在这里插入图片描述

3 DStream 创建

Spark Streaming 原生支持一些不同的数据源。

3.1 RDD队列

测试过程中,可以通过使用ssc.queueStream(queueOfRDDs)来创建DStream,每一个推送到这个队列中的RDD,都会作为一个DStream处理。

import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

import scala.collection.mutable

object RDDQueueDemo {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("RDDQueueDemo").setMaster("local[2]")
    // 1. 创建SparkStreaming的入口对象: StreamingContext  参数2: 表示事件间隔   内部会创建 SparkContext
    val ssc: StreamingContext = new StreamingContext(conf, Seconds(5))
    val sc: SparkContext = ssc.sparkContext
    // 2. 创建一个可变队列
    val queue: mutable.Queue[RDD[Int]] = mutable.Queue[RDD[Int]]()
    val rddDs: InputDStream[Int] = ssc.queueStream(queue, true)
    rddDs.reduce(_ + _).print()

    // 3.开始接收数据
    ssc.start()
    // 4.循环的方式向队列中添加 RDD
    for (elem <- 1 to 5) {
      queue += sc.parallelize(1 to 100 - elem)
      Thread.sleep(2000)
    }
    // 5.等待计算结束(要么手动退出,要么出现异常)才退出主程序
    ssc.awaitTermination()
  }
}

在这里插入图片描述

3.2 自定义数据源

其实就是自定义接收器
需要继承Receiver,并实现onStart、onStop方法来自定义数据源采集。
自定义数据源,实现监控某个端口号,获取该端口号内容。

import java.io.{BufferedReader, InputStreamReader}
import java.net.Socket
import java.nio.charset.StandardCharsets
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.receiver.Receiver

object MySource {
  def apply(host: String, port: Int): MySource = new MySource(host, port)
}

class MySource(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_ONLY) {

  //开始接收数据
  override def onStart(): Unit = {
    // 启动一个新的线程来接收数据
    new Thread() {
      override def run(): Unit = {
        receive()
      }
    }.start()
  }

  // 此方法用来接收数据
  def receive(): Unit = {
    val socket: Socket = new Socket(host, port)
    val reader: BufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream, StandardCharsets.UTF_8))
    var line: String = null
    while (!isStopped && (line = reader.readLine()) != null) {
      //将数据发送给spark
      store(line)
    }
    //关流
    reader.close()
    socket.close()
    // 重启任务
    restart("Trying to connect again")
  }
 override def onStop(): Unit = {
  }
}

代码实现

import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
object MySourceDemo {
  def main(args: Array[String]): Unit = {
    System.setProperty("hadoop.home.dir", "F:\\hadoop\\hadoop-2.7.2")
    val conf = new SparkConf().setAppName("MySourceDemo").setMaster("local[2]")
    // 1. 创建SparkStreaming的入口对象: StreamingContext  参数2: 表示事件间隔   内部会创建 SparkContext
    val ssc: StreamingContext = new StreamingContext(conf, Seconds(5))
    // 2. 监听端口
    val lines: ReceiverInputDStream[String] = ssc.receiverStream(MySource("192.168.199.101", 9999))
    //3.处理数据
    val words: DStream[String] = lines.flatMap(_.split(" "))
    val wordAndOne: DStream[(String, Int)] = words.map(x => (x, 1))
    val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    //4.显示结果
    result.print()
    //5.开始接收数据
    ssc.start()
    //6.等待计算结束(要么手动退出,要么出现异常)才退出主程序
    ssc.awaitTermination()
    ssc.stop(false)
  }
}

在这里插入图片描述
在这里插入图片描述

3.3 Kafka 数据源

在工程中需要引入 Maven 依赖 spark-streaming-kafka_2.11来使用它。
包内提供的 KafkaUtils 对象可以在 StreamingContext和JavaStreamingContext中以你的 Kafka 消息创建出 DStream。
两个核心类:KafkaUtils、KafkaCluster
导入依赖:

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

3.3.1高级API

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

object kafkaDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("kafkaDemo").setMaster("local[2]")
    val ssc: StreamingContext = new StreamingContext(conf, Seconds(5))

    //kafka配置参数
    //kafka参数声明
    val brokers = "master-1:9092,master-2:9092,slave-1:9092"
    val topic = "first"
    val group = "bigdata"
    val deserialization = "org.apache.kafka.common.serialization.StringDeserializer"
    val kafkaParams = Map(
      ConsumerConfig.GROUP_ID_CONFIG -> group,
      ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> brokers,
      ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> deserialization,
      ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> deserialization

    )

    //    val dStream = KafkaUtils.createDirectStream(ssc,kafkaParams,Set(topic))
    val dStream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
      ssc, kafkaParams, Set(topic))

    dStream.print()
    //数据处理
    val wordAndOne: DStream[(String, Int)] = dStream.flatMap({
      x => x._2.split(" ")
    }).map(x => (x, 1))
    val wordAndCount: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    wordAndCount.print()

//    dStream.print()
    //开始接收数据
    ssc.start()
    // 等待计算结束(要么手动退出,要么出现异常)才退出主程序
    ssc.awaitTermination()
  }

}

执行结果:
在这里插入图片描述
在这里插入图片描述

4 DStream转换

DStream 上的原语与 RDD 的类似,分为Transformations(转换)和Output Operations(输出)两种,此外转换操作中还有一些比较特殊的原语,如:updateStateByKey()、transform()以及各种Window相关的原语。

4.1 无状态转换操作

无状态转化操作就是把简单的RDD转化操作应用到每个批次上,也就是转化DStream中的每一个RDD。部分无状态转化操作列在了下表中。
在这里插入图片描述

4.2 有状态转换操作

4.2.1 updateStateByKey

updateStateByKey操作允许在使用新信息不断更新状态的同时能够保留他的状态.
需要做两件事情:

  1. 定义状态. 状态可以是任意数据类型
  2. 定义状态更新函数. 指定一个函数, 这个函数负责使用以前的状态和新值来更新状态.
import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object StreamingWordCount02 {
  def main(args: Array[String]): Unit = {
    System.setProperty("hadoop.home.dir", "F:\\hadoop\\hadoop-2.7.2")
    System.setProperty("HADOOP_USER_NAME", "root")
    val conf = new SparkConf().setAppName("StreamingWordCount02").setMaster("local[2]")
    val ssc: StreamingContext = new StreamingContext(conf, Seconds(5))
    val dStream: ReceiverInputDStream[String] = ssc.socketTextStream("master-1", 9999, StorageLevel.MEMORY_ONLY)
    val wordAndOne: DStream[(String, Int)] = dStream.flatMap(_.split("\\W+")).map((_, 1))
    // 设置检查点: 使用updateStateByKey必须设置检查点
    ssc.sparkContext.setCheckpointDir("hdfs://master-1:9000/checkpoint2")

    //    val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_ + _)
    val result: DStream[(String, Int)] = wordAndOne.updateStateByKey[Int](updateFunction _)
    result.print()
    ssc.start()
    ssc.awaitTermination()
  }

  def updateFunction(newValue: Seq[Int], runningCount: Option[Int]): Option[Int] = {
    // 新的总数和状态进行求和操作
    // a /: b表示a元素逐次与b数组中所有数执行后面()中的内容
    val newCount: Int = (0 /: newValue) (_ + _) + runningCount.getOrElse(0)
    Some(newCount)
  }

}

执行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2.2 window 操作

Spark Streaming 也提供了窗口计算, 允许执行转换操作作用在一个窗口内的数据.
默认情况下, 计算只对一个时间段内的RDD进行, 有了窗口之后, 可以把计算应用到一个指定的窗口内的所有 RDD 上.
一个窗口可以包含多个时间段. 基于窗口的操作会在一个比StreamingContext的批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。
在这里插入图片描述
观察上图, 窗口在 DStream 上每滑动一次, 落在窗口内的那些 RDD会结合在一起, 然后在上面操作产生新的 RDD, 组成了 window DStream.
在上面图的情况下, 操作会至少应用在 3 个数据单元上, 每次滑动 2 个时间单位. 所以, 窗口操作需要 2 个参数:

  1. 窗口长度 – 窗口的持久时间(执行一次持续多少个时间单位)(图中是 3)
  2. 滑动步长 – 窗口操作被执行的间隔(每多少个时间单位执行一次).(图中是 2 )
    注意: 这两个参数必须是源 DStream 的 interval 的倍数.

4.2.2.1 reduceByKeyAndWindow

reduceByKeyAndWindow(reduceFunc: (V, V) => V, windowDuration: Duration)

val wordAndOne: DStream[(String, Int)] = words.map((_, 1))
/*
参数1: reduce 计算规则
参数2: 窗口长度
参数3: 窗口滑动步长. 每隔这么长时间计算一次.
 */
val count: DStream[(String, Int)] =
wordAndOne.reduceByKeyAndWindow((x: Int, y: Int) => x + y,Seconds(15), Seconds(10))

在这里插入图片描述

4.2.2.2 reduceByKeyAndWindow

reduceByKeyAndWindow(reduceFunc: (V, V) => V, invReduceFunc: (V, V) => V, windowDuration: Duration, slideDuration: Duration)
比没有invReduceFunc高效. 会利用旧值来进行计算.
invReduceFunc: (V, V) => V 窗口移动了, 上一个窗口和新的窗口会有重叠部分, 重叠部分的值可以不用重复计算了. 第一个参数就是新的值, 第二个参数是旧的值.

ssc.sparkContext.setCheckpointDir("hdfs://hadoop201:9000/checkpoint")
val count: DStream[(String, Int)] =
    wordAndOne.reduceByKeyAndWindow((x: Int, y: Int) => x + y,(x: Int, y: Int) => x - y,Seconds(15), Seconds(10))

4.2.2.3 window

window(windowLength, slideInterval)
基于对源 DStream 窗化的批次进行计算返回一个新的 Dstream

4.2.2.4 countByWindow

countByWindow(windowLength, slideInterval)
返回一个滑动窗口计数流中的元素的个数。

4.2.2.5 countByValueAndWindow

countByValueAndWindow(windowLength, slideInterval, [numTasks])
对(K,V)对的DStream调用,返回(K,Long)对的新DStream,其中每个key的的对象的v是其在滑动窗口中频率。如上,可配置reduce任务数量。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值