版本:Spark 2.4.0
1. Overview
SparkingStreaming是对Spark API的一种扩展,用于处理实时数据流。
2. Basic Concepts
Maven依赖
org.apache.spark
spark-streaming_2.12
2.4.1
provided
如果数据源来自 Kafka 或 Flume等,需要添加如下依赖
Source | Artifact |
---|---|
Kafka | spark-streaming-kafka-0-10_2.12 |
Flume | spark-streaming-flume_2.12 |
2.1 创建StreamingContext
import org.apache.spark.*;
import org.apache.spark.streaming.api.java.*;
SparkConf conf = new SparkConf().setAppName(appName).setMaster(master);
JavaStreamingContext ssc = new JavaStreamingContext(conf, new Duration(1000));
或者
import org.apache.spark.streaming.api.java.*;
JavaSparkContext sc = ... //existing JavaSparkContext
JavaStreamingContext ssc = new JavaStreamingContext(sc, Durations.seconds(1));
意味着:StreamingContext中包含SparkContext
创建了context后,接下来:
- 定义数据源,创建输入DStreams
- 对DStreams使用转换和输出操作,定义流式计算
- 使用
streamingContext.start()
开始接收数据并计算 - 使用
streamingContext.awaitTermination()
等待处理过程结束(手动结束或错误退出) - 使用
streamingContext.stop()
可以手动结束处理过程
注意:
- 一旦一个context开始,就无法设置和增加新的流式计算
- 一旦一个context结束,它无法被restart
- 一个JVM里同时只能有一个活跃的StreamingContext
- StreamingContext调用stop()方法同时会停掉SparkContext,stop()方法有参数可以不停掉SparkContext
- 一个SparkContext可以被复用来创建多个StreamingContext,停止前一个StreamingContext(不停止sparkcontext)后即可创建下一个StreamingContext
2.2 DStreams(Discretized Streams,离散数据流)
SparkStreaming抽象出DStreams来表示连续的数据流。
DStreams内部使用一个连续的RDD串表示,每个RDD分别表示一个时间间隔内的数据。
从内部看,对于DStreams的操作(例如flatMap)被分别应用到各个RDD上。
从整体看,一个DStream(lines)经过转换操作(flatMap)后变为另一个DStream(words)
2.3 Input DStreams and Receivers
对于所有的Input DStream(除了文件系统),都需要使用一个Receiver来接收数据源的数据,并保存到Spark内存中用于Spark计算。
Spark Streaming支持两类流式数据源:
- 基础数据源:StreamingContext API 可以直接获取(文件系统和socket连接)
- 高级数据源:需要使用额外的类来获取(Kafka, Flume等)
注意:
当使用local模式运行一个Spark Streaming程序时,一般使用local[n]
作为master URL(n 要大于运行的receiver数),否则分配的线程只够运行receiver,而没有多余的线程来处理数据了。
同理,cluster模式时分配给Spark Streaming程序的核数要大于运行的receiver数。
2.3.1 基础数据源
- 文件系统
从任一与HDFS API 兼容的文件系统中读取文件上的数据,可以使用streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)
直接读取并创建DStream。
对于text 文件,使用streamingContext.textFileStream(dataDirectory)
读取。
Spark Streaming会监控目录,所有目录下的文件都会被处理。
需要注意的是:文件的完整性无法保证,有可能文件还没有写完就被读进DStream,那么后续的写入就会丢失。
如果在未被监控的目录下写文件,文件写完后再移动到监控目录,文件的完整性虽然保证了,但是移动文件会花费时间。
从自定义Receiver获取的Streams
通过RDD队列构造一个Stream
主要用于测试,streamingContext.queueStream(queueOfRDDs)
2.3.2 高级数据源
- Kafka: Spark Streaming 2.4.1 is compatible with Kafka broker versions 0.8.2.1 or higher.
- Flume: Spark Streaming 2.4.1 is compatible with Flume 1.6.0.
- Kinesis: Spark Streaming 2.4.1 is compatible with Kinesis Client Library 1.2.1.
2.3.3 Receiver
Flume、Kafka等数据源的接收需要使用Receiver,也可以自定义Receiver(extends Receiver
)。
Receiver的可靠性
- 可靠性Receiver:当Receiver接收到数据,保存到Spark(备份成功)后,返回确认消息。
- 不可靠Receiver:Receiver接收到数据后,不向数据源返回确认消息。
2.4 Transformations on DStreams
2.4.1 基本算子操作
Transformation | Meaning |
---|---|
map(func) | 源DStream中的每个element通过函数func后得到一个新的DStream |
flatMap(func) | 类似于map,但是每个input item可以被映射成0或多个output item |
filter(func) | 源DStream中的每个element通过函数func,只保留func结果为true的内容,得到一个新的DStream |
repartition(numPartitions) | 改变源DStream的partition数(新并行度) |
union(otherStream) | 将源DStream和另一个DStream合并,得到一个新的DStream |
count() | 返回一个由single-element RDDs组成的DStream,各single-element是各RDD内的elements的个数 |
reduce(func) | 返回一个由single-element RDDs组成的DStream,各single-element是利用函数func聚合各RDD内的elements得到的,函数func必须满足交换律和结合律,这样才能并行计算 |
countByValue() | 源DStream的element的类型是K,则返回一个新的DStream(K,Long),其中各K对应的值是源DStream的每个RDD里各K出现的频率 |
transform(func) | 源DStream的各RDD,通过一个RDD-to-RDD函数func,得到一个新的DStream,此方法可在DStream上使用任意RDD操作 |
updateStateByKey(func) | 对源DStream的各key的原state和新value使用函数func,得到新的state,最后得到一个新的DStream |
reduceByKey(func, [numTasks]) | 源DStream的RDD是(K,V)格式的pair,利用函数func对每个RDD的各K进行聚合得到V |
join(otherStream, [numTasks]) | 两个DStream的RDD分别是(K, V)和(K, W)格式的pair,返回一个新的格式为(K, (V,W)) pair的DStream |
cogroup(otherStream, [numTasks]) | 两个DStream的RDD分别是(K, V)和(K, W)格式的pair,返回一个新的格式为(K, Seq[V], Seq[W]) tuple的DStream |
总结:对DStream的Transformation操作,其实就是将此操作作用于DStream内的各RDD上,得到各新的RDD,最后得到新的DStream
2.4.2 滑动窗口操作
窗口操作包含两个参数:
- window length:窗口长度,即窗口的持续时间(上图是3 time)
- sliding interval:滑动间隔,即窗口操作的间隔时间(上图是2 time)
这两个参数必须是源DStream批处理间隔的整数倍(上图是1 time)
Transformation | Meaning |
---|---|
window(windowLength, slideInterval) | 源DStream基于窗口间隔转换为一个新的DStream |
countByWindow(windowLength, slideInterval) | 返回一个新的DStream,此DStream的内容是各滑动窗口的elements的个数 |
reduceByWindow(func, windowLength, slideInterval) | 返回一个由single-element RDDs组成的DStream,各single-element是利用函数func聚合各滑动窗口内的elements得到的,函数func必须满足交换律和结合律,这样才能并行计算 |
reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]) | 源DStream的RDD是(K,V)格式的pair,利用函数func对每个滑动窗口的各K进行聚合得到V |
countByValueAndWindow(windowLength, slideInterval, [numTasks]) | 源DStream的element的类型是K,则返回一个新的DStream(K,Long),其中各K对应的值是源DStream的每个滑动窗口里各K出现的频率 |
2.4.3 join操作
join:返回的数据集是
pair( 左右两边key的交集, 该交集key对应的左右两边的value(没有填None)组成的pair )
leftOuterJoin:返回的数据集是
pair( 左边的key, 该key对应的左右两边的value(没有填None)组成的pair )
rightOuterJoin:返回的数据集是
pair( 右边的key, 该key对应的左右两边的value(没有填None)组成的pair )
fullOuterJoin:返回的数据集是
pair( 左右两边key的并集, 该并集key对应的左右两边的value(没有填None)组成的pair )
2.5 Output Operations on DStreams
2.5.1 输出操作
Output Operation | Meaning |
---|---|
print() | 在Driver进程中打印DStream的每个批次的前十个element |
saveAsTextFiles(prefix, [suffix]) | 将DStream的内容保存为text文件,每个批次间隔的文件命名为prefix-TIME_IN_MS[.suffix] |
saveAsObjectFiles(prefix, [suffix]) | 将DStream的内容保存为可序列化对象的SequenceFiles,每个批次间隔的文件命名为prefix-TIME_IN_MS[.suffix] |
saveAsHadoopFiles(prefix, [suffix]) | 将DStream的内容保存为Hadoop文件,每个批次间隔的文件命名为prefix-TIME_IN_MS[.suffix] |
foreachRDD(func) | 最通用的输出操作,对DStream中每个RDD使用函数func,func中可以将RDD中的数据写入外部系统。注意:函数func是在Driver进程 中执行的,而且一般包含RDD的Action操作 |
2.5.2 foreachRDD的使用
- 错误使用一
dstream.foreachRDD(rdd -> {
Connection connection = createNewConnection(); // executed at the driver
rdd.foreach(record -> {
connection.send(record); // executed at the worker
});
});
dstream.foreachRDD(func)
在Driver进程中运行
rdd.foreach(func)
在各Worker进程中运行
因此要想正确运行,需要将connection(在Driver上创建)序列化并发送给各Worker,而一般connection不会再机器间传递,因此会出错。
- 错误使用二
dstream.foreachRDD(rdd -> {
rdd.foreach(record -> {
Connection connection = createNewConnection();
connection.send(record);
connection.close();
});
});
此方法会为RDD的每个record都创建一个connection,性能降低。
- 改进方法
dstream.foreachRDD(rdd -> {
rdd.foreachPartition(partitionOfRecords -> {
Connection connection = createNewConnection();
while (partitionOfRecords.hasNext()) {
connection.send(partitionOfRecords.next());
}
connection.close();
});
});
为rdd的每个partition创建一个connection,此partition中的所有record都使用这一个connection。
- 二次改进
dstream.foreachRDD(rdd -> {
rdd.foreachPartition(partitionOfRecords -> {
// ConnectionPool is a static, lazily initialized pool of connections
Connection connection = ConnectionPool.getConnection();
while (partitionOfRecords.hasNext()) {
connection.send(partitionOfRecords.next());
}
ConnectionPool.returnConnection(connection); // return to the pool for future reuse
});
});
创建一个connection pool,复用pool内connection。
2.6 Caching / Persistence
类似RDD,DStream允许开发者保存数据到内存。
对DStream使用persist()
方法,会将DStream的每个RDD自动保存到内存中。
基于窗口的操作(reduceByWindow、reduceByKeyAndWindow等)和基于状态的操作(updateStateByKey等)默认会自动保存到内存中。
对于接收来自网络的input stream(kafka、flume等),默认的持久化级别是复制数据到两个节点来容错。
2.7 Checkpointing
Checkpoint两类数据:
- Metadata checkpointing:将计算信息保存到用于容错的地方(例如HDFS),可用于恢复运行driver进程的节点。元数据包括:
- Configuration:创建streaming application的配置
- DStreams operations:DStream操作集合
- Incomplete batches:job在排队但是还没有完成的批次
- Data checkpointing:保存RDD到可靠存储。
2.7.1 何时使用Checkpointing
- Usage of stateful transformations
- Recovering from failures of the driver running the application
2.7.2 如何配置Checkpointing
在一个容错、可靠的文件系统(例如HDFS)中设置一个目录,将checkpoint information 保存到该目录。
// Create a factory object that can create and setup a new JavaStreamingContext
JavaStreamingContextFactory contextFactory = new JavaStreamingContextFactory() {
@Override public JavaStreamingContext create() {
JavaStreamingContext jssc = new JavaStreamingContext(...); // new context
JavaDStream lines = jssc.socketTextStream(...); // create DStreams
...
jssc.checkpoint(checkpointDirectory); // set checkpoint directory
return jssc;
}
};
// Get JavaStreamingContext from checkpoint data or create a new one
JavaStreamingContext context = JavaStreamingContext.getOrCreate(checkpointDirectory, contextFactory);
// Do additional setup on context that needs to be done,
// irrespective of whether it is being started or restarted
context. ...
// Start the context
context.start();
context.awaitTermination();
2.8 Deploying Applications
2.8.1 发布
运行一个Spark Streaming应用程序,需要如下东西:
Cluster with a cluster manager
运行任何Spark应用程序的基础需求Package the application JAR
编译打包streaming application成一个JAR,如果使用spark-submit
运行application,那么不需要把Spark和Spark Streaminig打进JAR。如果application中使用了高级数据源(Kafka等),则需要把这些额外的依赖打进JARConfiguring sufficient memory for the executors
Configuring checkpointing
Configuring automatic restart of the application driver
为了driver能从失败中自动恢复,运行streaming application时必须监控driver进程,如果失败了必须重启driver。Configuring write-ahead logs
Spark 1.2后,可以使用write-ahead logs来获得高容错率。如果设置了write-ahead logs,那么从receiver接收的所有数据都会写入一个write-ahead log,并保存到配置的checkpoint directory。Setting the max receiving rate
配置receiver的最大接收速率
2.8.2 升级
运行中的Spark Streaming application升级的两种方法:
启动升级后的Spark Streaming application,与之前的application并行运行,一旦升级后的application热身完毕,则可以停掉之前的application。
注意:此方法需要数据源支持发送数据到两个地方。优雅的停掉现在运行的application(
JavaStreamingContext.stop(...)
)以确保接收的数据被处理完后才shutdown,然后启动升级的application。
注意:此方法需要数据源支持缓存(Kafka、Flume)。