storm和 SparkStreaming的区别
- Storm 是纯实时处理数据, SparkStreaming 微批处理数据,可以通过控制间隔时间做到实时处理.sparkStreaming 相对于storm来说,吞吐量大
- storm擅长处理简单的汇总型业务,sparkStreaming擅长处理复杂业务,storm相对于sparkStreaming来说轻量级,SparkStreaming中可以使用core或者sql或者机器学习
- storm的事务与SparkStreaming不同,sparkStreaming可以管理事务
- storm支持动态的资源调度,spark也支持
sparkStreaming简易模型
SparkStreaming是7x24小时不间断运行,sparkStreaming启动之后,首先会生成一个job,这个job有一个receiver task来接受数据。task每隔一段时间将接收来的数据封装到一个batch中,这段时间就是 batchInterval,生成的每个batch又被封装到一个RDD中,这个RDD又被分装到一个DStream中,sparkStreaming底层操作的就是DStream.DStream有自己的Transformation类算子,懒执行,需要DStream的OutputOperator类算子触发执行
假设 batchInterval=5s,
生成一个DStream时间是5s,集群处理一批数据时间是3s,0-5s集群一直接收数据,5-8s一边接收一边处理,8-10s只是接收数据,这样每一批次会造成集群的“休息”2s,集群资源利用不充分
如果集群处理一批次的时间是8s,0-5s只是接收数据,5-10一边接收数据,一边处理数据,10-13s一边接收数据,一边处理数据,这样批次数据越堆积越多,如果接收来的数据放在内存中,会有orm问题,如果内存不足放磁盘,也会加大数据处理的延迟问题
最好状态是:batchInterval=5s,集群处理一批次数据时间也是5s
sparkstreaming模拟
- SparkStreaming 注意:
- 1.需要设置local[2],因为一个线程是读取数据,一个线程是处理数据
- 2.创建StreamingContext两种方式,如果采用的是StreamingContext(conf,Durations.seconds(5))这种方式,不能在new SparkContext
- 3.Durations 批次间隔时间的设置需要根据集群的资源情况以及监控每一个job的执行时间来调节出最佳时间。
- 4.SparkStreaming所有业务处理完成之后需要有一个output operato操作
- 5.StreamingContext.start()straming框架启动之后是不能在次添加业务逻辑
- 6.StreamingContext.stop()无参的stop方法会将sparkContext一同关闭,stop(false) ,默认为true,会一同关闭
- 7.StreamingContext.stop()停止之后是不能在调用start
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object StreamingTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setMaster("local[2]")
conf.setAppName("streamingTest")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc,Durations.seconds(5))
// val ssc = new StreamingContext(conf,Durations.seconds(5)) // 两种创建方式,如果使用了 conf,那么就不要再前面穿件sparkContext对象,
// 从ssc中获取SparkContext()
// val context: SparkContext = ssc.sparkContext
ssc.sparkContext.setLogLevel("Error")
val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node004",9999)
val words: DStream[String] = lines.flatMap(one=>{one.split(" ")})
val pairWords: DStream[(String, Int)] = words.map(one=>{(one,1)})
val result: DStream[(String, Int)] = pairWords.reduceByKey((v1:Int, v2:Int)=>{v1+v2})
result.print(100)
ssc.start()
ssc.awaitTermination()
// ssc.stop(false)
}
}
foreachRDD
foreachRDD 注意事项:
- 1.foreachRDD中可以拿到DStream中的RDD,对RDD进行操作,但是一定要使用RDD的action算子触发执行,不然DStream的逻辑也不会执行
- 2.froeachRDD算子内,拿到的RDD算子操作外,这段代码是在Driver端执行的,可以利用这点做到动态的改变广播变量
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}
object StreamingTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setMaster("local[2]")
conf.setAppName("streamingTest")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc,Durations.seconds(5))
// val ssc = new StreamingContext(sc,Durations.seconds(5))
ssc.sparkContext.setLogLevel("Error")
val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node004",9999)
val words: DStream[String] = lines.flatMap(one=>{one.split(" ")})
val pairWords: DStream[(String, Int)] = words.map(one=>{(one,1)})
val result: DStream[(String, Int)] = pairWords.reduceByKey((v1:Int, v2:Int)=>{v1+v2})
result.print(100)
result.foreachRDD(pairRDD=>{
println("===========Driver 广播黑名单=============") //froeachRDD算子内,拿到的RDD算子操作外,这段代码是在Driver端执行的,可以利用这点做到动态的改变广播变量,这行每5s执行一次
pairRDD.filter(one => {
println("executor in filter ======="+one)
true
}).count()
// val resultRDD: RDD[(String, Int)] = newRDD.map(one => {
// println("executor in map *****" + one)
// one
// })
// resultRDD.count()
})
ssc.start()
ssc.awaitTermination()
// ssc.stop(false)
}
}
updateStateByKey
计算从开始到现在的所有的值
package com.scalaspark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
/**
* UpdateStateByKey 根据key更新状态
* 1、为Spark Streaming中每一个Key维护一份state状态,state类型可以是任意类型的, 可以是一个自定义的对象,那么更新函数也可以是自定义的。
* 2、通过更新函数对该key的状态不断更新,对于每个新的batch而言,Spark Streaming会在使用updateStateByKey的时候为已经存在的key进行state的状态更新
*/
object UpdateStateByKey {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setMaster("local[2]")
conf.setAppName("UpdateStateByKey")
val ssc = new StreamingContext(conf,Durations.seconds(5))
//设置日志级别
ssc.sparkContext.setLogLevel("ERROR")
val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node5",9999)
val words: DStream[String] = lines.flatMap(line=>{line.split(" ")})
val pairWords: DStream[(String, Int)] = words.map(word => {(word, 1)})
/**
* 根据key更新状态,需要设置 checkpoint来保存状态
* 默认key的状态在内存中 有一份,在checkpoint目录中有一份。
*
* 多久会将内存中的数据(每一个key所对应的状态)写入到磁盘上一份呢?
* 如果你的batchInterval小于10s 那么10s会将内存中的数据写入到磁盘一份
* 如果bacthInterval 大于10s,那么就以bacthInterval为准
*
* 这样做是为了防止频繁的写HDFS
*/
// ssc.checkpoint("./data/streamingCheckpoint")
ssc.sparkContext.setCheckpointDir("./data/streamingCheckpoint")
/**
* currentValues :当前批次某个 key 对应所有的value 组成的一个集合
* preValue : 以往批次当前key 对应的总状态值
*/
val result: DStream[(String, Int)] = pairWords.updateStateByKey((currentValues: Seq[Int], preValue: Option[Int]) => {
var totalValues = 0
if (!preValue.isEmpty) {
totalValues += preValue.get
}
for(value <- currentValues){
totalValues += value
}
Option(totalValues)
})
result.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
窗口操作 reduceByKeyAndWindow
每隔窗口滑动间隔时间 计算 窗口长度内的数据,按照指定的方式处理
计算每隔一段时间(a),某一段时间(b) 所获取的reduceByKey的值
例如:reduceByKeyAndWindow((v1:Int,v2:Int)=>(v1+v2),Durations.seconds(15),Durations.seconds(5))
假设每隔5s 1个batch,上图中窗口长度为15s,窗口滑动间隔5s
滑动间隔和窗口长度必须是 batchInterval 整数倍
package com.bjsxt.scalaspark.streaming
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Durations, StreamingContext}
import org.apache.spark.SparkConf
/**
* SparkStreaming 窗口操作
* reduceByKeyAndWindow
* 每隔窗口滑动间隔时间 计算 窗口长度内的数据,按照指定的方式处理
*/
object WindowOperator {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setAppName("windowOperator")
conf.setMaster("local[2]")
val ssc = new StreamingContext(conf,Durations.seconds(5))
ssc.sparkContext.setLogLevel("Error")
val lines: ReceiverInputDStream[String] = ssc.socketTextStream("node5",9999)
val words: DStream[String] = lines.flatMap(line=>{line.split(" ")})
val pairWords: DStream[(String, Int)] = words.map(word=>{(word,1)})
// val ds : DStream[(String,Int)] = pairWords.window(Durations.seconds(15),Durations.seconds(5))
/**
* 窗口操作普通的机制
*
* 滑动间隔和窗口长度必须是 batchInterval 整数倍
*/
val windowResult: DStream[(String, Int)] =
pairWords.reduceByKeyAndWindow((v1:Int, v2:Int)=>{v1+v2},Durations.seconds(15),Durations.seconds(5))
windowResult.print()
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
如果使用的计算每个滑动间隔内计算完成不了,我们可以进行优化,因为是在窗口长度内,都是将最前面的一个间隔去掉加入新的间隔。可以减少计算,所有可以优化,但是优化需要保留中间数据,也就是需要使用checkpoint,
/**
* 窗口操作优化的机制
*/
ssc.checkpoint("./data/streamingCheckpoint") // 使用了checkpoint
val windowResult: DStream[(String, Int)] = pairWords.reduceByKeyAndWindow(
(v1:Int, v2:Int)=>{v 1+v2},
(v1:Int, v2:Int)=>{v1-v2}, // 此处多了一个运算
Durations.seconds(15),
Durations.seconds(5))
也可以自己实现
val ds : DStream[(String,Int)] = pairWords.window(Durations.seconds(15),Durations.seconds(5))
transform算子
transform 算子可以拿到DStream中的RDD,对RDD使用RDD的算子操作,但是最后要返回RDD,返回的RDD又被封装到一个DStream
transform中拿到的RDD的算子外,代码是在Driver端执行的。可以做到动态的改变广播变量
textFileStream(dir)
- SparkStreaming 监控一个目录数据时
- 1.这个目录下已经存在的文件不会被监控到, 可以监控增加的文件
- 2.增加的文件必须是原子性产生。
saveAsTextFile
-
将SparkSteaming处理的结果保存在指定的目录中
-
saveAsTextFiles(prefix, [suffix]):
-
将此DStream的内容另存为文本文件。每批次数据产生的文件名称格式基于:prefix和suffix: “prefix-TIME_IN_MS[.suffix]”.
-
注意:
-
saveAsTextFile是调用saveAsHadoopFile实现的
-
spark中普通rdd可以直接只用saveAsTextFile(path)的方式,保存到本地,但是此时DStream的只有saveAsTextFiles()方法,没有传入路径的方法,
其参数只有prefix, suffix
其实:DStream中的saveAsTextFiles方法中又调用了rdd中的saveAsTextFile方法,我们需要将path包含在prefix中
Driver HA
因为SparkStreaming是7*24小时运行,Driver只是一个简单的进程,有可能挂掉,所以实现Driver的HA就有必要(如果使用的Client模式就无法实现Driver HA ,这里针对的是cluster模式)。Yarn平台cluster模式提交任务,AM(AplicationMaster)相当于Driver,如果挂掉会自动启动AM。这里所说的DriverHA针对的是Spark standalone和Mesos资源调度的情况下。实现Driver的高可用有两个步骤:
第一:提交任务层面,在提交任务的时候加上选项 --supervise,当Driver挂掉的时候会自动重启Driver。但是重启的driver不知道原来的任务的进度和逻辑,所以要进行代码层面恢复Driver
第二:代码层面,使用JavaStreamingContext.getOrCreate(checkpoint路径,JavaStreamingContextFactory)
Driver中元数据包括:
- 创建应用程序的配置信息。
- DStream的操作逻辑。
- job中没有完成的批次数据,也就是job的执行进度。
package com.scalaspark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Durations, StreamingContext}
/**
* Driver HA :
* 1.在提交application的时候 添加 --supervise 选项 如果Driver挂掉 会自动启动一个Driver
* 2.代码层面恢复Driver(StreamingContext)
*
*/
object SparkStreamingDriverHA {
//设置checkpoint目录
val ckDir = "./data/streamingCheckpoint"
def main(args: Array[String]): Unit = {
/**
* StreamingContext.getOrCreate(ckDir,CreateStreamingContext)
* 这个方法首先会从ckDir目录中获取StreamingContext【 因为StreamingContext是序列化存储在Checkpoint目录中,恢复时会尝试反序列化这些objects。
* 如果用修改过的class可能会导致错误,此时需要更换checkpoint目录或者删除checkpoint目录中的数据,程序才能起来。】
*
* 若能获取回来StreamingContext,就不会执行CreateStreamingContext这个方法创建,否则就会创建
*
*/
val ssc: StreamingContext = StreamingContext.getOrCreate(ckDir,CreateStreamingContext)
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
def CreateStreamingContext() = {
println("=======Create new StreamingContext =======")
val conf = new SparkConf()
conf.setMaster("local")
conf.setAppName("DriverHA")
val ssc: StreamingContext = new StreamingContext(conf,Durations.seconds(5))
ssc.sparkContext.setLogLevel("Error")
/**
* 默认checkpoint 存储:
* 1.配置信息
* 2.DStream操作逻辑
* 3.job的执行进度
* * 4.offset
*/
ssc.checkpoint(ckDir)
val lines: DStream[String] = ssc.textFileStream("./data/streamingCopyFile")
val words: DStream[String] = lines.flatMap(line=>{line.trim.split(" ")})
val pairWords: DStream[(String, Int)] = words.map(word=>{(word,1)})
val result: DStream[(String, Int)] = pairWords.reduceByKey((v1:Int, v2:Int)=>{v1+v2})
// result.print()
/**
* 更改逻辑,从ckdir中恢复driver,要是能恢复,新逻辑不执行,旧逻辑执行
*/
result.foreachRDD(pairRDD=>{
pairRDD.filter(one=>{
println("*********** filter *********")
true
})
pairRDD.foreach(println)
})
ssc
}
}
checkpoint会存储
- 配置信息
- DStream操作逻辑
- job的进度
- offset (kafka消息的位置)
目前只能 从 ckdir 中恢复StreamContext,就算代码更改了也还是会按照原来的代码逻辑执行。
除非更换 ckdir 目录或者清空ckdir目录,但是更换或者清空ckdir目录就无法恢复到原来driver执行任务的具体细节
driver HA 主要用在 当停止sparkStreaming时,再次启动,sparkStreaming可以接着上次消费的数据继续消费
如果我们想要既恢复dirver,又想改变代码逻辑,我们就要手动管理foffset,不让spark自己管理