SparkStreaming学习记录

版本: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后,接下来:

  1. 定义数据源,创建输入DStreams
  2. 对DStreams使用转换和输出操作,定义流式计算
  3. 使用streamingContext.start()开始接收数据并计算
  4. 使用streamingContext.awaitTermination()等待处理过程结束(手动结束或错误退出)
  5. 使用streamingContext.stop()可以手动结束处理过程

注意:

  1. 一旦一个context开始,就无法设置和增加新的流式计算
  2. 一旦一个context结束,它无法被restart
  3. 一个JVM里同时只能有一个活跃的StreamingContext
  4. StreamingContext调用stop()方法同时会停掉SparkContext,stop()方法有参数可以不停掉SparkContext
  5. 一个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 基础数据源
  1. 文件系统

从任一与HDFS API 兼容的文件系统中读取文件上的数据,可以使用streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)直接读取并创建DStream。
对于text 文件,使用streamingContext.textFileStream(dataDirectory)读取。

Spark Streaming会监控目录,所有目录下的文件都会被处理。

需要注意的是:文件的完整性无法保证,有可能文件还没有写完就被读进DStream,那么后续的写入就会丢失。
如果在未被监控的目录下写文件,文件写完后再移动到监控目录,文件的完整性虽然保证了,但是移动文件会花费时间。

  1. 从自定义Receiver获取的Streams

  2. 通过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两类数据:

  1. Metadata checkpointing:将计算信息保存到用于容错的地方(例如HDFS),可用于恢复运行driver进程的节点。元数据包括:
  • Configuration:创建streaming application的配置
  • DStreams operations:DStream操作集合
  • Incomplete batches:job在排队但是还没有完成的批次
  1. 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应用程序,需要如下东西:

  1. Cluster with a cluster manager
    运行任何Spark应用程序的基础需求

  2. Package the application JAR
    编译打包streaming application成一个JAR,如果使用spark-submit运行application,那么不需要把Spark和Spark Streaminig打进JAR。如果application中使用了高级数据源(Kafka等),则需要把这些额外的依赖打进JAR

  3. Configuring sufficient memory for the executors

  4. Configuring checkpointing

  5. Configuring automatic restart of the application driver
    为了driver能从失败中自动恢复,运行streaming application时必须监控driver进程,如果失败了必须重启driver。

  6. Configuring write-ahead logs
    Spark 1.2后,可以使用write-ahead logs来获得高容错率。如果设置了write-ahead logs,那么从receiver接收的所有数据都会写入一个write-ahead log,并保存到配置的checkpoint directory。

  7. Setting the max receiving rate
    配置receiver的最大接收速率

2.8.2 升级

运行中的Spark Streaming application升级的两种方法:

  1. 启动升级后的Spark Streaming application,与之前的application并行运行,一旦升级后的application热身完毕,则可以停掉之前的application。
    注意:此方法需要数据源支持发送数据到两个地方。

  2. 优雅的停掉现在运行的application(JavaStreamingContext.stop(...))以确保接收的数据被处理完后才shutdown,然后启动升级的application。
    注意:此方法需要数据源支持缓存(Kafka、Flume)。

你可能感兴趣的:(SparkStreaming学习记录)