Spark Streaming

1 Spark Streaming概述

1.1 定义

Spark Streaming用于流式数据的处理。

  1. Spark Streaming蜘的数据原很多,Kafka、Flume、HDFS等
  2. 数据输入后可以用Spark的高度抽象原:Map、Reduce、Join、Window等进行计算
  3. 结果也能保存在很多地方,如HDFS、各种数据库等

1.2 Spark Streaming架构原理

1.2.1 DStream概念

SparkCore => RDD
SparkSQL => DataFrame DataSet

DStream是随时间推移而收到的数据的序列。

Spark Streaming使用**离散化流(Discretized Stream)**作为抽象表示,叫做DStream。

在DStream内部,每个时间区间收到的数据都作为RDD存在,而DStream是由这些RDD所组成的序列(因此得名“离散化”)。所以,DStream就是对RDD在实时数据处理场景的一种封装
在这里插入图片描述

1.2.2 架构图

整体架构图

在这里插入图片描述

Spark Streaming架构图

在这里插入图片描述

1.2.3 背压机制

Spark 1.5以前版本,用户如果要限制Receiver的数据接收速率,可以通过设置静态配制参数“spark.streaming.receiver.maxRate”的值来实现,此举虽然可以通过限制接收速率,来适配当前的处理能力,防止内存溢出,但也会引入其它问题。比如:producer数据生产高于maxRate,当前集群处理能力也高于maxRate,这就会造成资源利用率下降等问题。
为了更好的协调数据接收速率与资源处理能力,1.5版本开始 Spark Streaming可以动态控制数据接收速率来适配集群数据处理能力。背压机制(即Spark Streaming Backpressure):根据JobScheduler反馈作业的执行信息来动态调整Receiver数据接收率
通过属性“spark.streaming.backpressure.enabled”来控制是否启用背压机制,默认值false,不启用

1.3 Spark Streaming特点

易用

容错

易整合到Spark体系

2 DStream入门

2.1 WordCount案例

在这里插入图片描述

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

<dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-streaming_2.12</artifactId>
        <version>3.0.0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.12</artifactId>
        <version>3.0.0</version>
    </dependency>
</dependencies>
package com.atguigu.sparkstreaming

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

object SparkStreaming01_WordCount {

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

        //1.初始化Spark配置信息
        val sparkConf = new SparkConf().setAppName("SparkStreaming").setMaster("local[*]")

        //2.初始化SparkStreamingContext
        val ssc = new StreamingContext(sparkConf, Seconds(3))

        //3.通过监控端口创建DStream,读进来的数据为一行行
        val lineDStream = ssc.socketTextStream("hadoop102", 9999)

        //3.1 将每一行数据做切分,形成一个个单词
        val wordDStream = lineDStream.flatMap(_.split(" "))

        //3.2 将单词映射成元组(word,1)
        val wordToOneDStream = wordDStream.map((_, 1))

        //3.3 将相同的单词次数做统计
        val wordToSumDStream = wordToOneDStream.reduceByKey(_+_)

        //3.4 打印
        wordToSumDStream.print()

        //4 启动SparkStreamingContext
        ssc.start()
        // 将主线程阻塞,主线程不退出
        ssc.awaitTermination()
    }
}
log4j.properties

log4j.rootLogger=error, stdout,R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}  %5p --- [%50t]  %-80c(line:%5L)  :  %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=../log/agent.log
log4j.appender.R.MaxFileSize=1024KB
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}  %5p --- [%50t]  %-80c(line:%6L)  :  %m%n
# 通过netcat发送数据
nc -lk 9999

2.2 WordCount解析

DStream是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark算子操作后的结果数据流。在内部实现上,每一批次的数据封装成一个RDD,一系列连续的RDD组成了DStream。对这些RDD的转换是由Spark引擎来计算。

说明:DStream中批次与批次之间计算相互独立。如果批次设置时间小于计算时间会出现计算任务叠加情况,需要多分配资源。通常情况,批次设置时间要大于计算时间

在这里插入图片描述

3 DStream创建

3.1 RDD队列

3.1.1 用法和说明

测试方法:

1 使用ssc.queueStream(queueOfRDDs)来创建

2 将每一个推送到这个队列中的RDD,都会作为DStream的一个批次处理

3.1.2 案例实操

需求:循环创建几个RDD,将RDD放入队列。通过SparkStreaming创建DStream,计算WordCount
在这里插入图片描述

package com.atguigu.sparkstreaming

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

import scala.collection.mutable

object SparkStreaming02_RDDStream {

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

        //1.初始化Spark配置信息
        val conf = new SparkConf().setAppName("SparkStreaming").setMaster("local[*]")

        //2.初始化SparkStreamingContext
        val ssc = new StreamingContext(conf, Seconds(4))

        //3.创建RDD队列
        val rddQueue = new mutable.Queue[RDD[Int]]()

        //4.创建QueueInputDStream
        // oneAtATime = true 默认,一次读取队列里面的一个数据
        // oneAtATime = false, 按照设定的批次时间,读取队列里面数据
        val inputDStream = ssc.queueStream(rddQueue, oneAtATime = false)

        //5.处理队列中的RDD数据
        val sumDStream = inputDStream.reduce(_+_)

        //6.打印结果
        sumDStream.print()

        //7.启动任务
        ssc.start()

        //8.循环创建并向RDD队列中放入RDD
        for (i <- 1 to 5) {
            rddQueue += ssc.sparkContext.makeRDD(1 to 5)
            Thread.sleep(2000)
        }

        ssc.awaitTermination()
    }
}

说明:如果一个批次中有多个RDD进入队列,最终计算前都会合并到一个RDD计算

3.2 自定义数据源接收器

3.2.1 用法和说明

需要继承Receiver,并实现onStart、onStop方法来自定义数据源采集。

在这里插入图片描述

3.2.2 案例实操

需求:自定义数据源,实现监控某个端口号,获取该端口号内容。

// 使用自定义的数据源采集数据
package com.atguigu.sparkstreaming

import java.io.{BufferedReader, InputStreamReader}
import java.net.Socket
import java.nio.charset.StandardCharsets

import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.receiver.Receiver
import org.apache.spark.streaming.{Seconds, StreamingContext}

object SparkStreaming03_CustomerReceiver {

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

        //1.初始化Spark配置信息
        val sparkConf = new SparkConf().setMaster("local[*]").setAppName("sparkstreaming")

        //2.初始化SparkStreamingContext
        val ssc = new StreamingContext(sparkConf, Seconds(5))

        //3.创建自定义receiver的Streaming
        val lineDStream = ssc.receiverStream(new CustomerReceiver("hadoop102", 9999))

        //4.将每一行数据做切分,形成一个个单词
        val wordDStream = lineDStream.flatMap(_.split(" "))

        //5.将单词映射成元组(word,1)
        val wordToOneDStream = wordDStream.map((_, 1))

        //6.将相同的单词次数做统计
        val wordToSumDStream = wordToOneDStream.reduceByKey(_ + _)

        //7.打印
        wordToSumDStream.print()

        //8.启动SparkStreamingContext
        ssc.start()
        ssc.awaitTermination()
    }
}
// 自定义数据源
/**
* @param host : 主机名称
 * @param port : 端口号
 *  Receiver[String] :返回值类型:String
 *  StorageLevel.MEMORY_ONLY: 返回值存储级别
 */
class CustomerReceiver(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_ONLY) {

    // receiver刚启动的时候,调用该方法,作用为:读数据并将数据发送给Spark
    override def onStart(): Unit = {
			 //在onStart方法里面创建一个线程,专门用来接收数据
        new Thread("Socket Receiver") {
            override def run() {
                receive()
            }
        }.start()
    }

    // 读数据并将数据发送给Spark
    def receive(): Unit = {

        // 创建一个Socket
        var socket: Socket = new Socket(host, port)

        // 字节流读取数据不方便,转换成字符流buffer,方便整行读取
        val reader = new BufferedReader(new InputStreamReader(socket.getInputStream, StandardCharsets.UTF_8))

        // 读取数据
        var input: String = reader.readLine()

        //当receiver没有关闭并且输入数据不为空,就循环发送数据给Spark
        while (!isStopped() && input != null) {
            store(input)
            input = reader.readLine()
        }

        // 如果循环结束,则关闭资源
        reader.close()
        socket.close()

        //重启接收任务
        restart("restart")
    }

    override def onStop(): Unit = {}
}

3.3 Kafka数据源(面试、开发重点)

3.3.1 版本选型

在这里插入图片描述

**ReceiverAPI:**需要一个专门的Executor去接收数据,然后发送给其他的Executor计算。

存在的问题:接收的数据的Executor和计算的Executor的速度有所不同,特不是在接收数据的速度大于计算的数据,会导致计算数据的节点数据积压,容易OOM。早期版本中使用这种方式,当前版本不使用。

**DirectAPI:**是由计算的Executor来主动消费Kafka的数据,速度由自身控制。

目前Spark3.0.0以上版本只有Direct模式

http://spark.apache.org/docs/2.4.7/streaming-kafka-integration.html

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

在这里插入图片描述

不同版本的offset存储位置:

  1. 0.8 ReceiverAPI offset默认存储在 Zookeeper中

  2. 0.8 DirectAPI offset默认存储在 CheckPoint

    ​ 手动维护在 MySQL 等有事务的存储系统中

  3. 0.10 DirectorAPI offset默认存储在 _consumer_offsets 系统主题

    ​ 手动维护在 MySQL等有事务的存储系统

3.3.2 Kafka 0.10 Direct模式

通过SparkStreaming从Kafka读取数据,并将读取过来的数据做简单计算,最终打印到控制台。

在这里插入图片描述

<dependency>
     <groupId>org.apache.spark</groupId>
     <artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
     <version>3.0.0</version>
</dependency>
package com.atguigu.sparkstreaming

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

object SparkStreaming04_DirectAuto {

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

        //1.创建SparkConf
        val sparkConf: SparkConf = new SparkConf().setAppName("sparkstreaming").setMaster("local[*]")

        //2.创建StreamingContext
        val ssc = new StreamingContext(sparkConf, Seconds(3))

        //3.定义Kafka参数:kafka集群地址、消费者组名称、key序列化、value序列化
        val kafkaPara: Map[String, Object] = Map[String, Object](
            ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
            ConsumerConfig.GROUP_ID_CONFIG -> "atguiguGroup",
            ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> "org.apache.kafka.common.serialization.StringDeserializer",
            ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer]
        )

        //4.读取Kafka数据创建DStream
        val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
            ssc,
            LocationStrategies.PreferConsistent, //优先位置
            ConsumerStrategies.Subscribe[String, String](Set("testTopic"), kafkaPara)// 消费策略:(订阅多个主题,配置参数)
        )

        //5.将每条消息(KV)的V取出
        val valueDStream: DStream[String] = kafkaDStream.map(record => record.value())

        //6.计算WordCount
        valueDStream.flatMap(_.split(" "))
            .map((_, 1))
            .reduceByKey(_ + _)
            .print()

        //7.开启任务
        ssc.start()
        ssc.awaitTermination()
    }
}
# 分别启动Zookeeper和Kafka集群
zk.sh start
kf.sh start

# 创建一个Kafka的Topic主题testTopic。两个分区
[atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --replication-factor 1 --partitions 2 --topic testTopic

# 查看Topic列表
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 -list

# 查看topic详情
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --describe --topic testTopic

# 创建Kafka生产者
bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic testTopic

# 创建kafka消费者
bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --from-beginning --topic testTopic

# 查看_consumer_offsets 主题中的offset
bin/kafka-consumer-groups.sh --bootstrap-server hadoop102:9092 --describe --group atguiguGroup

GROUP        TOPIC    PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  
atguiguGroup    testTopic       0              13                 13    

4 DStream转换

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

4.1 无状态转化操作

把RDD转化操作应用到DStream每个批次上,每个批次相互独立,自己算自己的。

4.1.1 常规无状态转化操作

DStream的部分无状态转化操作在下表中,都是DStream自己的API。

注意:针对键值对的DStream转化操作,要添加 import StreamingContext._ 才能在Scala中使用,比如 reduceByKey()

函数名称目的Scala示例函数签名
map()对DStream中的每个元素应用给定函数,返回由各元素输出的元素组成的DStreamds.map(x=>x + 1)f: (T) -> U
flatMap()对DStream中的每个元素应用给定函数,返回由各元素输出的迭代器组成的DStreamds.flatMap(x => x.split(" "))f: T -> Iterable[U]
filter()返回由给定DStream中通过筛选的元素组成的DStreamds.filter(x => x != 1)f: T -> Boolean
repartition()改变DStream的分区数ds.repartition(10)N / A
reduceByKey()将每个批次中键相同的记录规约ds.reduceByKey( (x, y) => x + y)f: T, T -> T
groupByKey()将每个批次中的记录根据键分组ds.groupByKey()N / A

注意:尽管这些函数看起来像作用在整个流上一样,但事实上每个DStream在内部是由许多RDD批次组成,且无状态转化操作是分别应用到每个RDD(一个批次的数据)上的。

4.1.2 Transform

需求:通过Transform可以将DStream每一批次的数据直接转换为RDD的算子操作。

package com.atguigu.sparkstreaming

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

object SparkStreaming05_Transform {

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

        //1 创建SparkConf
        val sparkConf = new SparkConf().setMaster("local[*]").setAppName("sparkstreaming")

        //2 创建StreamingContext
        val ssc = new StreamingContext(sparkConf, Seconds(3))

        //3 创建DStream
        val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)

        // 在Driver端执行,全局一次
        println("111111111:" + Thread.currentThread().getName)

        //4 转换为RDD操作
        val wordToSumDStream: DStream[(String, Int)] = lineDStream.transform(

            rdd => {
                // 在Driver端执行(ctrl+n JobGenerator),一个批次一次
                println("222222:" + Thread.currentThread().getName)

                val words: RDD[String] = rdd.flatMap(_.split(" "))
	
                val wordToOne: RDD[(String, Int)] = words.map(x=>{

                    // 在Executor端执行,和单词个数相同
                    println("333333:" + Thread.currentThread().getName)

                    (x, 1)
                })

                val result: RDD[(String, Int)] = wordToOne.reduceByKey(_ + _)

                result
            }
        )

        //5 打印
        wordToSumDStream.print

        //6 启动
        ssc.start()
        ssc.awaitTermination()
    }
}

4.2 有状态转化操作

计算当前批次RDD时,需要用到历史RDD数据。

4.2.1 UpdateStateByKey

updateStateByKey()用于键值对形式的DStream,可以记录历史批次状态。例如可以实现累加WordCount。updateStateByKey()参数中需要传递一个函数,在函数内部可以根据需求对新数据和历史状态进行整合处理,返回一个新的DStream。

注意:使用updateStateByKey需要对检查点目录进行配置,会使用检查点来保存状态。

缺点:

  1. checkpoint小文件过多
  2. checkpoint记录最后一次时间戳,再次启动时会将间隔时间的周期再执行一次

需求:更新版的WordCount

package com.atguigu.sparkstreaming

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

object SparkStreaming06_updateStateByKey {
  def main(args: Array[String]): Unit = {
    //TODO 1 创建SparkConf配置文件,并设置App名称
    val conf = new SparkConf().setAppName("SparkStreaming").setMaster("local[*]")
    //TODO 2 利用SparkConf创建StreamingContext对象
    val ssc = new StreamingContext(conf, Seconds(3))

    //使用updateStateByKey必须要设置检查点目录
    ssc.checkpoint("D:\\IdeaProjects\\sparkstreamingtest\\checkpoint")

    //3 获取一行数据
    val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)

    //4 切割数据
    val wordDStream: DStream[String] = lineDStream.flatMap(_.split(" "))

    //5 转换数据结构
    val word2oneDStream: DStream[(String, Int)] = wordDStream.map((_, 1))

    //6 使用updateStateByKey来更新状态,统计从运行开始以来单词总的次数
    val result: DStream[(String, Int)] = word2oneDStream.updateStateByKey(updateFunc)

    result.print()

    //TODO 3 启动StreamingContext,并且阻塞主线程,一直执行
    ssc.start()
    ssc.awaitTermination()
  }

  //定义更新状态方法,参数seq为当前批次单词次数,state为以往批次单词次数
  def updateFunc = (seq:Seq[Int],state:Option[Int]) => {
    //获取当前批次单词的和
    val currentCount: Int = seq.sum
    //获取历史状态的数据
    val stateCount: Int = state.getOrElse(0)
    //将当前批次的和加上历史状态的数据和,返回
    Some(currentCount + stateCount)
  }
}

原理说明:

在这里插入图片描述

4.2.2 WindowOperation

Window Operation可以设置窗口的大小和活动窗口的间隔来动态获取当前Streaming的允许状态,所有基于窗口的操作都需要两个参数,放不下为窗口时长和滑动步长。

窗口时长:计算内容的时间范围

滑动步长:隔多久除法一次计算

这两者都必须为采集批次大小的整数倍

在这里插入图片描述

4.2.3 Window

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

需求:统计WordCount:3秒一个批次,窗口12秒,滑步6秒。

package com.atguigu.sparkstreaming

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

object SparkStreaming07_window {

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

        // 1 初始化SparkStreamingContext
        val conf = new SparkConf().setMaster("local[*]").setAppName("sparkstreaming")
        val ssc = new StreamingContext(conf, Seconds(3))

        // 2 通过监控端口创建DStream,读进来的数据为一行行
        val lines = ssc.socketTextStream("hadoop102", 9999)

        // 3 切割=》变换
        val wordToOneDStream = lines.flatMap(_.split(" "))
            .map((_, 1))

        // 4 获取窗口返回数据
        val wordToOneByWindow: DStream[(String, Int)] = wordToOneDStream.window(Seconds(12), Seconds(6))

        // 5 聚合窗口数据并打印
        val wordToCountDStream: DStream[(String, Int)] = wordToOneByWindow.reduceByKey(_+_)
        wordToCountDStream.print()

        // 6 启动=》阻塞
        ssc.start()
        ssc.awaitTermination()
    }
}

如果有多批数据进入窗口,最终也会通过window操作变成统一的RDD处理。

在这里插入图片描述

4.2.4 reduceByKeyAndWindow

reduceByKeyAndWindow(func, windowLength, slideInterval, [num Tasks])
// 当在一个(K,V)对的DStream上调用此函数,会返回一个新(K,V)对的DStream,此处通过对滑动窗口中批次数据使用reduce函数来整合每个key的value值。

需求:统计WordCount:3秒一个批次,窗口12秒,滑步6秒。

package com.atguigu.sparkstreaming

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

object SparkStreaming08_reduceByKeyAndWindow {

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

        // 1 初始化SparkStreamingContext
        val conf = new SparkConf().setAppName("sparkstreaming").setMaster("local[*]")
        val ssc = new StreamingContext(conf, Seconds(3))

        // 保存数据到检查点
        ssc.checkpoint("./ck")

        // 2 通过监控端口创建DStream,读进来的数据为一行行
        val lines = ssc.socketTextStream("hadoop102", 9999)

        // 3 切割=》变换
        val wordToOne = lines.flatMap(_.split(" "))
                         .map((_, 1))

        // 4 窗口参数说明: 算法逻辑,窗口12秒,滑步6秒
        val wordCounts = wordToOne.reduceByKeyAndWindow((a: Int, b: Int) => (a + b), Seconds(12), Seconds(6))

        // 5 打印
        wordCounts.print()

        // 6 启动=》阻塞
        ssc.start()
        ssc.awaitTermination()
    }
}

4.2.5 reduceByKeyAndWindow(反向Reduce)

reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [num Tasks])
// 这个函数是上述函数的变化版本,每个窗口的reduce值都是通过用前一个窗的reduce值来递增计算。通过reduce进入到滑动窗口数据并“反向reduce”离开窗口的旧数据来实现这个操作。一个例子是随着窗口滑动对keys的“加”“减”计数。通过前边介绍可以想到,这个函数只适用于“可逆的reduce函数”,也就是这些reduce函数有相应的“反reduce”函数(以参数invFunc形式传入)。如前述函数,reduce任务的数量通过可选参数来配置。

需求:统计WordCount:3秒一个批次,窗口12秒,滑步6秒。

package com.atguigu.sparkstreaming

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

object SparkStreaming09_reduceByKeyAndWindow_reduce {

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

        // 1 初始化SparkStreamingContext
        val conf = new SparkConf().setMaster("local[*]").setAppName("sparkstreaming")
        val ssc = new StreamingContext(conf, Seconds(3))

        // 保存数据到检查点
        ssc.checkpoint("./ck")

        // 2 通过监控端口创建DStream,读进来的数据为一行行
        val lines = ssc.socketTextStream("hadoop102", 9999)

        // 3 切割 =》变换
        val wordToOne = lines.flatMap(_.split(" "))
            .map((_, 1))

        // 4 窗口参数说明: 算法逻辑,窗口12秒,滑步6秒
        /*
        val wordToSumDStream: DStream[(String, Int)]= wordToOne.reduceByKeyAndWindow(
            (a: Int, b: Int) => (a + b),
            (x: Int, y: Int) => (x - y),
            Seconds(12),
            Seconds(6)
        )*/

        // 处理单词统计次数为0的问题
        val wordToSumDStream: DStream[(String, Int)]= wordToOne.reduceByKeyAndWindow(
            (a: Int, b: Int) => (a + b),
            (x: Int, y: Int) => (x - y),
            Seconds(12),
            Seconds(6),
            new HashPartitioner(2),
            (x:(String, Int)) => x._2 > 0
        )

        // 5 打印
        wordToSumDStream.print()

        // 6 启动=》阻塞
        ssc.start()
        ssc.awaitTermination()
    }
}

4.2.6 Window的其他操作

  1. countByWindow(windowLength, slideInterval): 返回一个滑动窗口技术流中的元素个数
  2. reduceByWindow(func, windowLength, slideInterval): 通过使用自定义函数整合滑动区间流元素来创建一个新的离散化数据流

5 DStream输出

DStream通常将数据输出到,外部数据库或屏幕上

DStream与RDD中的惰性求值类似,如果一个DStream及其派生出的DStream都没有被执行输出操作,那么这些DStream就都不会被求值。如果StreamingContext中没有设定输出操作,整个Context就都不会启动。

1)输出操作API如下:

saveAsTextFiles(prefix, [suffix])
// 以text文件形式存储这个DStream的内容。每一批次的存储文件名基于参数中的prefix和suffix。“prefix-Time_IN_MS[.suffix]”。

saveAsObjectFiles(prefix, [suffix])
// 以Java对象序列化的方式将DStream中的数据保存为 SequenceFiles 。每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]"。 

saveAsHadoopFiles(prefix, [suffix])
// 将Stream中的数据保存为 Hadoop files。每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]"。

注意:以上操作都是每一批次写出一次,会产生大量小文件,在生产环境,很少使用

print()
// 在运行流程序的驱动结点上打印DStream中每一批次数据的最开始10个元素。这用于开发和调试。

foreachRDD(func)
// 这是最通用的输出操作,即将函数func用于产生DStream的每一个RDD。其中参数传入的函数func应该实现将每一个RDD中数据推送到外部系统,如将RDD存入文件或者写入数据库。

在企业开发中通常采用foreachRDD(),它用来对DStream中的RDD进行任意计算。这和transform()有些类似,都可以让我们访问任意RDD。在foreachRDD()中,可以重用我们在Spark中实现的所有行动操作(action算子)。比如,常见的用例之一是把数据写到如MySQL的外部数据库中。

package com.atguigu.sparkstreaming

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

object SparkStreaming10_output {

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

        // 1 初始化SparkStreamingContext
        val conf = new SparkConf().setMaster("local[*]").setAppName("sparkstreaming")
        val ssc = new StreamingContext(conf, Seconds(3))

        // 2 通过监控端口创建DStream,读进来的数据为一行行
        val lineDStream = ssc.socketTextStream("hadoop102", 9999)

        // 3 切割=》变换
        val wordToOneDStream = lineDStream.flatMap(_.split(" "))
            .map((_, 1))

        // 4 输出
        wordToOneDStream.foreachRDD(
            rdd=>{
                // 在Driver端执行(ctrl+n JobScheduler),一个批次一次
                // 在JobScheduler 中查找(ctrl + f)streaming-job-executor
                println("222222:" + Thread.currentThread().getName)

                rdd.foreachPartition(
                    //5.1 测试代码
                    iter=>iter.foreach(println)

                    //5.2 企业代码
                    //5.2.1 获取连接
                    //5.2.2 操作数据,使用连接写库
                    //5.2.3 关闭连接
                )
            }
        )

        // 5 启动=》阻塞
        ssc.start()
        ssc.awaitTermination()
    }
}

注意:

  1. 连接不能写在Driver层面(序列化)
  2. 如果写在foreach则每个RDD中的每一条数据都创建,得不偿失
  3. 增加foreachPartition,在分区创建(获取)

6 优雅关闭

流式任务需要7*24小时执行,但是有时涉及到升级代码需要主动停止程序,但是分布式程序,没办法做到一个个进程去杀死,所以配置优雅的关闭就显得至关重要了。

关闭方式:使用外部文件系统来控制内部程序关闭。

package com.atguigu.sparkstreaming

import java.net.URI
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.ReceiverInputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext, StreamingContextState}

object SparkStreaming11_stop {

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

        //1.初始化Spark配置信息
        val sparkconf = new SparkConf().setMaster("local[*]").setAppName("sparkStreaming")

			 // 设置优雅的关闭
        sparkconf.set("spark.streaming.stopGracefullyOnShutdown", "true")

        //2.初始化SparkStreamingContext
        val ssc: StreamingContext = new StreamingContext(sparkconf, Seconds(3))

        // 接收数据
        val lineDStream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop102", 9999)
        // 执行业务逻辑
        lineDStream.flatMap(_.split(" "))
                .map((_,1))
                .print()

        // 开启监控程序
        new Thread(new MonitorStop(ssc)).start()

        //4 启动SparkStreamingContext
        ssc.start()

        // 将主线程阻塞,主线程不退出
        ssc.awaitTermination()
    }
}

// 监控程序
class MonitorStop(ssc: StreamingContext) extends Runnable{

    override def run(): Unit = {
        // 获取HDFS文件系统
        val fs: FileSystem = FileSystem.get(new URI("hdfs://hadoop102:8020"),new Configuration(),"atguigu")

        while (true){
            Thread.sleep(5000)
            // 获取/stopSpark路径是否存在
            val result: Boolean = fs.exists(new Path("hdfs://hadoop102:8020/stopSpark"))

            if (result){

                val state: StreamingContextState = ssc.getState()
                // 获取当前任务是否正在运行
                if (state == StreamingContextState.ACTIVE){
                    // 优雅关闭
                    ssc.stop(stopSparkContext = true, stopGracefully = true)
                    System.exit(0)
                }
            }
        }
    }
}
#  
sbin/start-dfs.sh
hadoop fs -mkdir /stopSpark
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值