目录
一、SparkStreaming简介
SparkStreaming是一个对实时数据流进行高通量、容错处理的流式处理系统,可以对多种数据源(如Kdfka、Flume、Twitter、Zero和TCP 套接字)进行类似Map、Reduce和Join等复杂操作,并将结果保存到外部文件系统、数据库或应用到实时仪表盘。
在内部,其按如下方式运行。Spark Streaming接收到实时数据流同时将其划分为分批,这些数据的分批将会被Spark的引擎所处理从而生成同样按批次形式的最终流。
Spark Streaming提供了被称为离散化流或者DStream的高层抽象,这个高层抽象用于表示数据的连续流。
创建DStream的两种方式:
1. 由Kafka,Flume取得的数据作为输入数据流。
2. 在其他DStream进行的高层操作。
在内部,DStream被表达为RDDs的一个序列。
这个指南会指引你如何利用DStreams编写Spark Streaming的程序。你可以使用诸如Scala,Java或者Python来编写Spark Streaming的程序。文中的标签可以让你在不同编程语言间切换。
注意:少量的API在Python中要么是不可用的,要么是和其他有差异的。
二、DStream简介
Spark Streaming提供了表示连续数据流的、高度抽象的被称为离散流的DStream。假如外部数据不断涌入,按照一分钟切片,每个一分钟内部的数据是连续的(连续数据流),而一分钟与一分钟的切片却是相互独立的(离散流)。
DStream是Spark Streaming特有的数据类型。
1.Dstream离散流由一系列连续的RDD组成,每个RDD都包含了确定时间间隔内的数据。
Dstream可以看做一组RDDs,即RDD的一个序列:
2.对DStream中数据的各种操作也是映射到内部的RDD上来进行的
park的RDD可以理解为空间维度,Dstream的RDD理解为在空间维度上又加了个时间维度。例如上图,数据流进切分为四个分片,内部处理逻辑都是相同的,只是时间维度不同。
Spark与Spark Streaming区别:
Spark -> RDD:transformation action + RDD DAG
Spark Streaming -> Dstream:transformation output(它不能让数据在中间激活,必须保证数据有输入有输出) + DStreamGraph
任何对DStream的操作都会转变为对底层RDD的操作(通过算子):
3.Dstream的输入源包括基本源(文件系统和Socket(套接字)连接)和高级源( Kafka、Flume、Kinesis、Twitter 等,额外增加类依赖)
三、DStream操作
DStream的转化操作分位无状态和有状态两种:
1、在无状态转化操作中,每个批次的处理不依赖于之前批次的数据,像RDD的map,filter,reduceBykey等操作都是无状态转化操作。
2、有状态转化操作需要使用之前批次的数据或者中间结果来计算当前批次的数据。有状态转化操作包括基于滑动窗口的转化操作和追踪状态变化的转化操作。
1. 无状态转化操作
无状态转化操作就是简单把RDD转化操作应用到每个批次上,部分无状态转化操作如下图。注:针对键值对的DStream转化操作(如reduceByKey())要添加import StreamingContext._才能在Scala中使用。
普通的转换操作:map、flatMap、flter、union、count、join等
注:尽管这些函数看起来像所用在整个流上,其实是每个DStream在内部有许多RDD批次组成,这些操作是分别应用到每个RDD上的。
2、有状态转化操作
DSteam的有状态转化操作是跨时间区间跟踪数据的操作,即一些先前批次的数据也被用来在新的批次中计算结果。有状态转化操作需要在你的 StreamingContext 中打开检查点机制来确保容错性,设置检查点:
ssc.checkpoint("hdfs://...")
2.1窗口转换操作:
允许你通过滑动窗口对数据进行转换,如countByWindow、 reduceByKeyAndWindow等,(批处理间隔、窗口间隔和滑动间隔)
基于窗口的操作会在一个比StreamingContext的批次间隔更长的时间范围内,通过整合多个批次的结果,计算出整个窗口的结果。所有基于窗口的参数都需要两个参数:窗口时长和滑动步长,两个必须是StreamContext的批次间隔的正数倍。
窗口时长控制每次计算最近的多少个批次的数据,其实就是最近的windowDuration/batchInterval个批次。如果一个以10s为批次间隔的源DStream,要创建一个最近30s的时间窗口(即最近三个批次),就把windowDuratioin设为30s
滑块步长的默认值与批次间隔相等,用来控制对新的DStream进行计算的间隔。如果源DStream批次间隔为10s,且我们希望每两个批次计算一次窗口结果,就应该把滑动步长设置为20s
在Scala中用window()对窗口进行计数:
val accessLogsWindow = accessLogsDStream.window(Seconds(30), Seconds(10))
val windowCounts = accessLogsWindow.count()
reduceByWindow() 和 reduceByKeyAndWindow() 可以对每个窗口更高效地进行归约操作。两种不同是后者可以逆运算(-),可以记录进入窗口和离开窗口的数据:
val ipDStream = accessLogsDStream.map{entry => entry.getIpAddress()}
val ipAddressRequestCount = ipDStream.countByValueAndWindow(Seconds(30), Seconds(10))
val requestCount = accessLogsDStream.countByWindow(Seconds(30), Seconds(10))
2.2 跟踪状态的转化操作,updateStateByKey操作:
有时我们需要在DStream中跨批次维护状态(例如跟踪用户访问网站的会话),针对这种情况,updateStateByKey()提供了对一个状态变量的访问,用于键值对形式的DStream。updateStateByKey()提供了一个update(events, oldState) 函数,接收与某键相关的事件以及该键之前对应的状态,返回这个键对应的新状态。
event:是在当前批次中收到的事件的列表(可能为空)
oldState:是一个可选的状态对象,存放在Option内;如果一个键没有之前的状态,这个值可以空缺
newState:由函数返回,也以Option形式存在;可以返回一个空的Option来表示要删除该状态
updateStateByKey() 的结果会是一个新的 DStream,其内部的 RDD 序列是由每个时间区间对应的(键,状态)对组成的。
在Scala中使用updateStateByKey()运行响应代码的计数:
def updateRunningSum(values: Seq[Long], state: Option[Long]) = {
Some(state.getOrElse(0L) + values.size)
}
val responseCodeDStream = accessLogsDStream.map(log => (log.getResponseCode(), 1L))
val responseCodeCountDStream =
responseCodeDStream.updateStateByKey(updateRunningSum _)
2.3.输出操作:允许DStream的数据被输出到外部系统,如数据库或文件系统,有print()、foreachRDD(func)、saveAsTextFiles()、 saveAsHadoopFiles()等
输出操作指定了对流数据经转化操作得到的数据所要执行的操作(例如把结果推入外数据库或者屏幕上)
与RDD中的惰性求值类似,如果一个DStream及其派生出的DStream都没有被执行输出操作,那么这些DStream就都不会被求值。如果StreamingContext中没有设定输出操作,整个context就都不会启动。
一旦调试好了程序,就可以使用输出操作来保存结果了。在Scala中将DStream保存为文件:
ipAddressRequestCount.saveAsTextFiles("outputDir", "txt")
还可以用saveAsHadoopFiles()函数,接收Hadoop输出格式存储。存储SequenceFile Spark Streaming比较特殊:
val writableIpAddressRequestCount = ipAddressRequestCount.map {
(ip, count) => (new Text(ip), new LongWritable(count)) }
writableIpAddressRequestCount.saveAsHadoopFiles[
SequenceFileOutputFormat[Text, LongWritable]]("outputDir", "txt")
还有一个通用的输出操作foreachRDD(),用来对DStream中的RDD运行任意计算:
ipAddressRequestCount.foreachRDD { rdd =>
rdd.foreachPartition { partition =>
// 打开到存储系统的连接(比如一个数据库的连接)
partition.foreach { item =>
// 使用连接把item存到系统中
}
// 关闭连接
}
}
2.4 transform(func)操作:允许DStream 上应用任意RDD-to-RDD函数
2.5 持久化:通过persist()方法将数据流存放在内存中,有利于高效的迭代运算
Spark Streaming 优缺点:
与传统流式框架相比,Spark Streaming 最大的不同点在于它对待数据是粗粒度的处理方式,即一次处理一小批数据,而其他框架往往采用细粒度的处理模式,即依次处理一条数据。Spark Streaming 这样的设计实现既为其带来了显而易见的优点,又引入了不可避免的缺点。
优点:
1. Spark Streaming 内部的实现和调度方式高度依赖 Spark 的 DAG 调度器和 RDD,这就决定了 Spark Streaming 的设计初衷必须是粗粒度方式的,同时,由于 Spark 内部调度器足够快速和高效,可以快速地处理小批量数据,这就获得准实时的特性。
2. Spark Streaming 的粗粒度执行方式使其确保“处理且仅处理一次”的特性,同时也可以更方便地实现容错恢复机制。
3. 由于 Spark Streaming 的 DStream 本质是 RDD 在流式数据上的抽象,因此基于 RDD 的各种操作也有相应的基于 DStream 的版本,这样就大大降低了用户对于新框架的学习成本,在了解 Spark 的情况下用户将很容易使用 Spark Streaming。
4. 由于 DStream 是在 RDD 上的抽象,那么也就更容易与 RDD 进行交互操作,在需要将流式数据和批处理数据结合进行分析的情况下,将会变得非常方便。
缺点:
1. Spark Streaming 的粗粒度处理方式也造成了不可避免的延迟。在细粒度处理方式下,理想情况下每一条记录都会被实时处理,而在 Spark Streaming 中,数据需要汇总到一定的量后再一次性处理,这就增加了数据处理的延迟,这种延迟是由框架的设计引入的,并不是由网络或其他情况造成的。