一、Transform
Transform允许DStream上执行任意的RDD-to-RDD函数。即使这些函数并没有在DStream的API中暴露出来,通过该函数可以方便的扩展Spark API。该函数每一批次调度一次。其实也就是对DStream中的RDD应用转换。
文字展示:
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreaming06_State_Transform {
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Wordshow")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf,Seconds(3))
//3.创建DStream
val lines = ssc.socketTextStream("localhost",9999)
//4.转换为RDD操作
//transform方法可以将底层RDD获取到后进行操作
//Code:Driver端
val newDS: DStream[String] = lines.transform(
rdd =>{
rdd.map(
str => {
str
}
)
}
)
val newDS1:DStream[String] = lines.map(
data => (
data
)
)
//5.打印
newDS.print()
//6、启动SparkStreamingContext任务
ssc.start()
ssc.awaitTermination()
}
}
打开natcat,连接展示:
wordcount计算:
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreaming06_State_Transform02 {
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf,Seconds(4))
//3.创建DStream
val lineDStream = ssc.socketTextStream("localhost",9999)
//4.转换为RDD操作
//转换为RDD操作
val wordAndCountDStream: DStream[(String, Int)] = lineDStream.transform(
rdd => {
val words: RDD[String] = rdd.flatMap(_.split(" "))
val wordAndOne: RDD[(String, Int)] = words.map((_, 1))
val value: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
value
}
)
//5.打印
wordAndCountDStream.print
//6、启动SparkStreamingContext任务
ssc.start()
ssc.awaitTermination()
}
}
1.1.2 join
两个流之间的join需要两个流的批次大小一致,这样才能做到同时触发计算。计算过程就是对当前批次的两个流中各自的RDD进行join,与两个RDD的join效果相同。
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreaming07_State_Join {
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDDStreaming")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf,Seconds(6))
//3.从端口获取数据创建流
val data9999 = ssc.socketTextStream("localhost",9999)
val data8888 = ssc.socketTextStream("localhost",8888)
//4.将两个流转换为KV类型
val map9999:DStream[(String,Int)] =data9999.map((_,9))
val map8888:DStream[(String,Int)] =data8888.map((_,8))
//5.流的JOIN
//所谓的DStream的join操作,其实就是两个RDD的join操作
val joinDS: DStream[(String,(Int,Int))]= map9999.join(map8888)
//6.打印
joinDS.print()
//7.启动任务
ssc.start()
ssc.awaitTermination()
}
}
二、 有状态转化操作
2.1 UpdateStateByKey
UpdateStateByKey原语用于记录历史记录,有时,我们需要在DStream中跨批次维护状态(例如流计算中累加wordcount)。针对这种情况,updateStateByKey()为我们提供了对一个状态变量的访问,用于键值对形式的DStream。给定一个由(键,事件)对构成的DStream,并传递一个指定如何根据新的事件更新每个键对应状态的函数,它可以构建出一个新的DStream,其内部数据为(键,状态) 对。updateStateByKey() 的结果会是一个新的DStream,其内部的RDD 序列是由每个时间区间对应的(键,状态)对组成的。updateStateByKey操作使得我们可以在用新信息进行更新时保持任意的状态。为使用这个功能,需要做下面两步:1. 定义状态,状态可以是一个任意的数据类型。2. 定义状态更新函数,用此函数阐明如何使用之前的状态和来自输入流的新值对状态进行更新。使用updateStateByKey需要对检查点目录进行配置,会使用检查点来保存状态。更新版的wordcount。
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreaming05_State {
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf,Seconds(4))
//使用有状态操作时,需要设定检查点路径
ssc.checkpoint("cp")
//3.无状态数据操作,只对当前的采集周期内的数据进行处理
//在某些场合下,需要保留数据统计结果(状态),实现数据的汇总
val datas = ssc.socketTextStream("localhost",9999)
val wordToOne = datas.map((_,1))
//val wordToCount = wordToOne.reduceByKey(_+_)
//updateStateByKey:根据key对数据的状态进行更新
//传递的参数中含有两个值
//第一个值表示相同的key的value数据
val state = wordToOne.updateStateByKey(
(seq:Seq[Int],buff:Option[Int]) => {
val newCount = buff.getOrElse(0)+ seq.sum
Option(newCount)
}
)
state.print()
//4、启动SparkStreamingContext任务
ssc.start()
ssc.awaitTermination()
}
}
三、WindowOperations(滑窗)
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreaming06_State_Windows {
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDDStreaming")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf,Seconds(3))
//3.从端口获取数据创建流
val lines = ssc.socketTextStream("localhost",9999)
val wordToOne =lines.map((_,1))
//窗口的范围应该是我们采集周期的整数倍
//窗口可以滑动的,但是我们默认情况下,一个采集周期进行滑动
//这样的话,可能会出现重复数据的计算,为了避免这样的情况,可以改变滑动的滑步(步长)
val windowDS:DStream[(String,Int)] = wordToOne.window(Seconds(6),Seconds(6))
val wordToCount = windowDS.reduceByKey(_+_)
//6.打印
wordToCount.print()
//7.启动任务
ssc.start()
ssc.awaitTermination()
}
}
Window Operations可以设置窗口的大小和滑动窗口的间隔来动态的获取当前Steaming的允许状态。所有基于窗口的操作都需要两个参数,分别为窗口时长以及滑动步长。
➢窗口时长:计算内容的时间范围;
➢滑动步长:隔多久触发一次计算。注意:这两者都必须为采集周期大小的整数倍。
WordCount第三版:3秒一个批次,窗口12秒,滑步6秒。
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreaming06_State_Windows02 {
def main(args: Array[String]): Unit = {
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDDStreaming")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf,Seconds(3))
ssc.checkpoint("cp")
//3.从端口获取数据创建流
val lines = ssc.socketTextStream("localhost",9999)
val wordToOne =lines.map((_,1))
//当我们窗口范围比较大,但是我们滑动幅度比较小,那么可采用增加数据和删除数据的方式
//无需重复计算,提升我们的性能
val windowDS:DStream[(String,Int)] =
wordToOne.reduceByKeyAndWindow(
(x:Int,y:Int) =>{x+y},
(x:Int,y:Int) =>{x-y},
Seconds(9),Seconds(3))
//6.打印
windowDS.print()
//7.启动任务
ssc.start()
ssc.awaitTermination()
}
}
关于Window的操作还有如下方法:
(1)window(windowLength,slideInterval): 基于对源DStream窗化的批次进行计算返回一个新的Dstream;
(2)countByWindow(windowLength,slideInterval): 返回一个滑动窗口计数流中的元素个数;
(3)reduceByWindow(func,windowLength,slideInterval): 通过使用自定义函数整合滑动区间流元素来创建一个新的单元素流;
(4)reduceByKeyAndWindow(func,windowLength,slideInterval, [numTasks]): 当在一个(K,V)对的DStream上调用此函数,会返回一个新(K,V)对的DStream,此处通过对滑动窗口中批次数据使用reduce函数来整合每个key的value值。
(5)reduceByKeyAndWindow(func,invFunc,windowLength,slideInterval, [numTasks]): 这个函数是上述函数的变化版本,每个窗口的reduce值都是通过用前一个窗的reduce值来递增计算。通过reduce进入到滑动窗口数据并”反向reduce”离开窗口的旧数据来实现这个操作。一个例子是随着窗口滑动对keys的“加”“减”计数。通过前边介绍可以想到,这个函数只适用于”可逆的reduce函数”,也就是这些reduce函数有相应的”反reduce”函数(以参数invFunc形式传入)。如前述函数,reduce任务的数量通过可选参数来配置。
val ipDStream = accessLogsDStream.map(logEntry => (logEntry.getIpAddress(), 1))
val ipCountDStream = ipDStream.reduceByKeyAndWindow(
{(x, y) => x + y},
{(x, y) => x -y},
Seconds(30),
Seconds(10))
//加上新进入窗口的批次中的元素//移除离开窗口的老批次中的元素//窗口时长// 滑动步长
countByWindow()和countByValueAndWindow()作为对数据进行计数操作的简写。countByWindow()返回一个表示每个窗口中元素个数的DStream,而countByValueAndWindow()返回的DStream则包含窗口中每个值的个数。
val ipDStream = accessLogsDStream.map{entry => entry.getIpAddress()}
val ipAddressRequestCount = ipDStream.countByValueAndWindow(Seconds(30), Seconds(10))
val requestCount = accessLogsDStream.countByWindow(Seconds(30), Seconds(10))
四、DStream输出
输出操作指定了对流数据经转化操作得到的数据所要执行的操作(例如把结果推入外部数据库或输出到屏幕上)。与RDD中的惰性求值类似,如果一个DStream及其派生出的DStream都没有被执行输出操作,那么这些DStream就都不会被求值。如果StreamingContext中没有设定输出操作,整个context就都不会启动。输出操作如下:
➢print():在运行流程序的驱动结点上打印DStream中每一批次数据的最开始10个元素。这用于开发和调试。在Python API中,同样的操作叫print()。
➢saveAsTextFiles(prefix, [suffix]):以text文件形式存储这个DStream的内容。每一批次的存储文件名基于参数中的prefix和suffix。”prefix-Time_IN_MS[.suffix]”。
➢saveAsObjectFiles(prefix, [suffix]):以Java对象序列化的方式将Stream中的数据保存为SequenceFiles. 每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]".Python中目前不可用。
➢saveAsHadoopFiles(prefix, [suffix]):将Stream中的数据保存为Hadoop files. 每一批次的存储文件名基于参数中的为"prefix-TIME_IN_MS[.suffix]"。Python API中目前不可用。
➢foreachRDD(func):这是最通用的输出操作,即将函数func 用于产生于stream的每一个RDD。其中参数传入的函数func应该实现将每一个RDD中数据推送到外部系统,如将RDD存入文件或者通过网络将其写入数据库。
通用的输出操作foreachRDD(),它用来对DStream中的RDD运行任意计算。这和transform() 有些类似,都可以让我们访问任意RDD。在foreachRDD()中,可以重用我们在Spark中实现的所有行动操作。比如,常见的用例之一是把数据写到诸如MySQL的外部数据库中。
注意:
1)连接不能写在driver层面(序列化)
2)如果写在foreach则每个RDD中的每一条数据都创建,得不偿失;
3)增加foreachPartition,在分区创建(获取)。
五、优雅的关闭
流式任务需要7*24小时执行,但是有时涉及到升级代码需要主动停止程序,但是分布式程序,没办法做到一个个进程去杀死,所有配置优雅的关闭就显得至关重要了。使用外部文件系统来控制内部程序关闭。
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext, StreamingContextState}
object SparkStreaming08_State_Close {
def main(args: Array[String]): Unit = {
/*
* 线程的关闭:
* val thread = new Thread()
* thread.start()
*
* thread.stop(); //强制关闭
*
* */
//1.初始化Spark配置信息
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDDStreaming")
//2.初始化SparkStreamingContext
val ssc = new StreamingContext(sparkConf,Seconds(3))
//3.从端口获取数据创建流
val lines = ssc.socketTextStream("localhost",9999)
val wordToOne =lines.map((_,1))
wordToOne.print()
//7.启动任务
ssc.start()
//如果想要关闭采集器,那么需要创建新的线程
//而且需要在第三方程序中增加关闭状态
new Thread(
new Runnable {
override def run(): Unit = {
//优雅的关闭
//计算节点不在接收新的数据,而是将现有的数据处理完毕,然后关闭
//Mysql:Table(stopSpark) =>Row => data
//Redis:Datas(K-V)
//ZK :/stopSpark
//HDFS :/StopSpark
/*
while (true) {
if (true) {
//获取SparkStreaming状态
val state:StreamingContextState = ssc.getState()
if (state == StreamingContextState.ACTIVE) {
ssc.stop(stopSparkContext = true,stopGracefully = true)
}
}
Thread.sleep(5000)
}
*/
Thread.sleep(5000)
val state:StreamingContextState = ssc.getState()
if (state == StreamingContextState.ACTIVE) {
ssc.stop(stopSparkContext = true,stopGracefully = true)
}
System.exit(0)
}
}
).start()
ssc.awaitTermination() //block阻塞main线程
}
}
运行结果:
六、恢复数据
package com.lzl.bigdata.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreaming09_Resume {
def main(args: Array[String]): Unit = {
val ssc = StreamingContext.getActiveOrCreate("cp",()=>{
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
val ssc = new StreamingContext(sparkConf,Seconds(3))
val lines = ssc.socketTextStream("localhost",9999)
val wordToOne =lines.map((_,1))
wordToOne.print()
ssc
})
ssc.checkpoint("cp")
ssc.start()
ssc.awaitTermination() //block阻塞main线程
}
}