Spark Streaming它是对Spark核心API的扩展,目的在于对实时数据流进行高吞吐、高容错的处理。Spark Streaming底层是Spark Core。
val conf = new SparkConf().setAppName(appName).setMaster(master)
val ssc = new StreamingContext(conf, Seconds(1))
val conf = new SparkConf().setAppName(appName).setMaster(master)
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc, Seconds(3))
注释:
离散流或DStream是Spark Streaming提供的基本抽象。它表示连续的数据流,可以是从源接受道德输入数据流,也可以是通过转换输入流生成的经过处理的数据流。在内部,DStream由一系列连续的RDDs表示,RDDs是Spark对不可变得分布式数据集的抽象。DStream中的每个RDD包含来自某个间隔(batch interval)的数据。
应用于DStream上的任何操作都转换为底层RDDs上的操作。例如:在下面将一个lines流转换为单词的示例(Network WordCount)中,flatMap操作应用于lines DStream中的每个RDD,以生成word DStream的RDDs。
这些底层的RDD转换是由Spark引擎计算的。DStream操作隐藏了这些细节中的大部分,并为开发人员提供了更高级别的API。详见后文DSteam API。
Input DStream 是表示从流媒体源接受的输入数据流的DStream,通常是第一个DStream。
Spark Streaming提供了两类内置流媒体源。
注意:
注意:textFileStream()参数必须是文件目录,但可以支持通配符如"hdfs://namenode:8020/logs/2020/*"
Spark将监视该目录任务新建的文件,一旦有新文件才会处理。所有文件要求有相同的数据格式,并且监视文件的修改时间二不是创建时间,注意更新文件内容不会被监视,一旦开始处理,这些文件必须不能在更改,因此如果文件被连续地追加,新的数据也不会被读取。文件流不需要运行接收器,因此,不需要分配内核
通过用于测试中。为了使用测试数据测试SparkSteaming应用程序,可以使用streamingContext.queueStream(queueOfRDDs)创建一个基于RDDs队列的DStream,每个进入队列的RDD都将被视为DStream中的一个批次数据,并且就像一个流进行处理。
自定义数据源即自定义Receiver。自定义接受器必须通过实现两个方法来扩展Receiver抽象类。
与RDDs类似,转换允许修改输入DStream中的数据。DStream支持许多在普通Spark RDD上可用的转换。一些常见的转换操作定义如下。
通过函数func传递源DStream的每个元素来返回一个新的DStream。
与map类似,但是每个输入项可以映射到0或多个输出项。
通过只选择func返回true的源DStream的记录来返回一个新的DStream。
通过创建更多或更傻的分区来改变DStream中的并行度。
返回一个新的DSream,它包含源DStream和otherDStream中元素的并集。
通过计算源DStream的DStream的每个RDD中的元素数量,返回一个新的单元素RDDsStreams。
通过使用函数func(接受两个参数并返回一个参数)聚合源DStream的每个RDD中的元素,返回一个新的单元素RDDs DStream。这个函数应该是结合律和交换律,这样才能并行计算。
当对类型为K的元素的DStream调用时,返回一个新的DStream(K,Long)对,其中每个键的值是他在源DStream的每个RDD中的频率。
当在(K,V)对的DStream上调用时,返回一个新的(K,V)对的Dsteam,其中每个键的值使用给定的reduce函数进行聚合。注意:在默认情况下,这将使用Spark的默认并行任务书(本地模式为2,而在集群模式下,改数量由配置属性spark.default.parallelism
决定)来进行分组。可以传递一个可选的numTasks参数来设置不同数量的任务。
当调用两个DStream(K,V)和(K,W)对的DStream时,返回一个新的(K,Seq[V],Seq[W])元祖DStream。
当调用(K,V)和(K,W)对的DStream时,返回一个新的(K,Seq[V],Seq[W])元祖DStream。
通过对源DStream的每个RDD应用一个RDD-to-RDD函数来返回一个新的DStream。这可以用来DStream上执行任意的RDD操作。
返回一个新的"状态"DStream,其中通过对键的前一个状态和键的新值应用给定的函数来更新每个键的状态。这可以用来维护每个键的任意状态数据。
相比RDD转换,DStream有两个特殊操作:UpdateStateByKey操作和Window操作。
输出操作允许将DStream的数据推送到外部系统,如数据库或文件系统。由于输出操作实际上允许外部系统使用转换后的数据,因此他们会触发所有DStream转换的实际执行(类似于RDDs的action)。
在运行流应用程序的驱动节点上打印DStream中每批数据的前十个元素。这对开发和调试非常有用。
将DStream的内容保存为文本文件。每个批处理间隔的文件名是根据前缀和后缀“prefix-TIME_IN_MS[.suffix]”生成的。
将DStream的内容保存为Hadoop文件。
将这个DStream的内容保存为序列化的java对象的序列文件。
将函数func应用于从流生成的每个RDD,这是最通用的输出操作。该函数应该将给每个RDD中的数据推送到外部系统,例如将RDD保存到文件中,或者通过网络将其写入数据库。请注意,func函数是在运行流应用程序的驱动程序进程中执行的,并且通常会有RDDactions,这将强制进行流RDDs的计算。
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkSQLSparkStreamingDemo extends App {
//TODO 创建一个spark StreamingContext对象
val conf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("DEMO01")
val ssc = new StreamingContext(conf,Seconds(5))
//TODO 创建SparkSession对象
val spark=SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
//TODO 使用spark streaming来进行wordcount
val inputDstream: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop101",5678)
//TODO 对输入的流进行操作
val wordDstream: DStream[String] = inputDstream.flatMap(_.split(" "))
wordDstream.foreachRDD(
rdd=>{
if (rdd.count()!=0){
val df1=rdd.map(x=>Word(x)).toDF()
df1.createOrReplaceTempView("words")
spark.sql(
"""
|select word,count(*)
|from words
|group by word
""".stripMargin).show()
}
}
)
//TODO 通过start() 启动消息采集和处理
ssc.start()
//TODO 等待程序终止
ssc.awaitTermination()
}
case class Word(word:String)
Spark Stream通过Push和Pull两种方式对接Flume数据源。以Saprk Streaming的角度来看,Push方式属于推送(由Flume向Spark推送)而Pull属于拉取(Spark 拉取Flume的输出)。
不论以何种方式,发开过程类似,都是由Spark Streaming对接Flume数据流,Flume作为Spark Streaming的数据源。Push和Pull两者的差别主要体现在Flume Sink的不同,而Flume Source与Channel不会受影响。在演示示例时,FlumeSource以nectcat为例,Channel为memory,重点关注SInk的变化。
当采用Push方式时,Flume架构为:netcat->memory->arvo。
这里的avro sink需要指定avro服务地址,容易混响的是avro服务有flume还是Spark启动问题。要搞清楚这个问题,首先Push方式在Spark端如何实现。
import org.apache.spark.SparkConf
import org.apache.spark.streaming.flume.FlumeUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkFlumePushDemo extends App {
val conf = new SparkConf().setAppName("flumeDemo01").setMaster("local[2]")
val ssc = new StreamingContext(conf, Seconds(5))
//TODO push方式
val flumeStream = FlumeUtils.createStream(ssc, "192.168.222.115", 55555)
flumeStream.map(x=>new String(x.event.getBody.array()).trim)
.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
flume的配置文件
agent.sources = s1
agent.channels = c1
agent.sinks = sk1
# 设置Source的类型为netcat,使用的channel为c1
agent.sources.s1.type = netcat
agent.sources.s1.bind = bigdata
agent.sources.s1.port = 11111
agent.sources.s1.channels = c1
agent.channels.c1.type = memory
agent.channels.c1.capacity = 1000
# AvroSink向Spark(55555)推送数据
# 使用push createStream
agent.sinks.sk1.type=avro
agent.sinks.sk1.hostname= bigdata
agent.sinks.sk1.port= 33333
agent.sinks.sk1.channel = c1
启动顺序:先启动jar文件 -> flume配置文件 ->启动telnet11111端口。
当采用Pull方式时,Flume架构为:netcat -> memory ->SparkSink
此Sink全类名为org.apache.spark.streaming.flume.sink.SparkSink,需要在Flume的".conf"文件中使用,该类包含在spark-stream-flume-sink组件中。所以还需要将次组件放在$FLUME_HOME/lib下。具体远吗如下图所示:
SparkSink由Flume启动avro服务,所以本上上任然是avroSink,如下图所示。然后由Spark连接后轮循数据,一位这Flume Agent应该先启动。
saprk代码
import org.apache.spark.SparkConf
import org.apache.spark.streaming.flume.FlumeUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkFlumePollDemo extends App {
val conf = new SparkConf().setAppName("flumeDemo02").setMaster("local[2]")
val ssc = new StreamingContext(conf, Seconds(5))
//TODO poll方式
val flumePollStream= FlumeUtils
.createPollingStream(ssc,"hadoop101",55555)
flumePollStream.map(x=>new String(x.event.getBody.array()).trim)
.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
flume代码
agent.sources = s1
agent.channels = c1
agent.sinks = sk1
#设置Source的内省为netcat,使用的channel为c1
agent.sources.s1.type = netcat
agent.sources.s1.bind = bigdata
agent.sources.s1.port = 44444
agent.sources.s1.channels = c1
#SparkSink,要求flume lib目录存在spark-streaming-flume-sink_2.11-x.x.x.jar
agent.sinks.sk1.type= org.apache.spark.streaming.flume.sink.SparkSink
agent.sinks.sk1.hostname= bigdata
agent.sinks.sk1.port=55555
agent.sinks.sk1.channel = c1
#设置channel信息
#内存模式
agent.channels.c1.type = memory
agent.channels.c1.capacity = 1000
执行顺序:先执行flume配置文件 ->spark的jar文件 ->开启端口
因为Kafka项目在0.8和0.10版本之间引入了新的消费之API,因此有两个单独的相应Spark Streaming包:
Receiver是最早的方式。Receiver方式通过Receiver来获取数据,是使用Kafka的High Level Consumer API来实现的。Receiver将Kafka数据源中获取的数据存储在Spark Executor的内存中,然后Spark Streaming 启动的job会去处理那些数据。但是,在默认的配置下,这种方式可能会因为顶层的故障而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制。该机制会同步地将接收到的kafka数据写入分布式文件系统上的雨鞋日志中。所以,及时底层节点出现了失败,也可以使用雨鞋日志中的数据进行恢复。
这种新的不基于 Receiver 的直接方式,是在 Spark 1.3 中引入的,从而能够确保更加健壮的机制。替代掉使用 Receiver 来接收数据后,这种方式会周期性地查询 Kafka,来获得每个topic+partition 的最新的 offset,从而定义每个 batch 的offset 的范围。当处理数据的 job 启动时,就会使用 Kafka 的简单 consumer api来获取 Kafka 指定 offset 范围的数据。
Direct 方式采用 Kafka 简单的 consumer api 方式来读取数据,此种方式不再需要专门 Receiver 来持续不断读取数据。当 batch 任务触发时,由 Executor 读取数据,并参与到其他 Executor 的数据计算过程中去。driver 来决定读取多少 offsets,并将 offsets 交由 checkpoints 来维护。将触发下次 batch 任务,再由 Executor 读 取 Kafka 数据并计算。从此过程我们可以发现 Direct 方式无需 Receiver 读取数据,而是需要计算时再读取数据,所以 Direct 方式的数据消费对内存的要求不高,只需要考虑批量计算所需要的内存即可。