目录
本篇所述翻译自Spark2.4.3版本的Spark Streaming Guide官方文档,是自己在学习期间发现网上资料包括书籍都混乱不堪因而感觉直接看官方文档比较靠谱,而很多同学英语水平受限所以就将其翻译下来供直接使用。注意这其中实际仅仅包含Spark Streaming如何使用即how而没有why。
总览
Spark Streaming建立在Spark API的基础之上,提供稳定,高吞吐量和故障恢复的数据流处理操作。Spark Streaming接受来自Kafka,Flume,Kinesis或者TCP套接字的数据源,采用map,reduce,join,window高级方法进行处理。最终处理的结果数据写入文件系统,数据库或者显示到dashboard。实际上,也可以在数据流上应用Spark的机器学习和图处理算法。
内部流程实际是Spark Streaming不断接受输入数据流并且将数据分成批次,一批一批的数据之后由Spark引擎进行处理得到最终的多批次的结果数据。
Spark Streaming提供了高级抽象,离散stream或者说DStream,他代表的是连续的数据流。DStream由来自Kafka,Flume,Knesis等数据源的输入数据或者其他DStream创建,而DStream内部是由一系列RDD组成。也就是说一个DStream对象代表着是这个数据流,而其中一个批次的数据表示成了一个RDD,作为该DStream对象中的一个RDD元素。
这个指南想要说明如何借助DStream编写Spark Streaming程序。支持Python,Scala,Java编程语言(这里只写Python)Python在Spark1.2才引入的。
快速入门
在深入Spark Streaming程序细节之前,应该先快速看一看Spaark Streaming程序的样貌。这里想要计数数据流中的单词数量,数据来源于TCP套接字。
首先导入StreamingContext,它是所有流处理函数的中心类,我们创建了一个本地的StreamingContext,有两个线程,批次时间间隔是1秒。
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
# 创建本地StreamingContext,2个线程,1s的时间间隔
sc = SparkContext("local[2]", "NetworkWordCount")
ssc = StreamingContext(sc, 1)
有了上下文环境就能创建表示流数据的DStream对象,接受TCP套接字数据,指定TCP套接字的主机名和端口号。
# 创建DStream,连接到本机的9999端口
lines = ssc.socketTextStream("localhost", 9999)
lines就是一个DStream对象,代表流数据。DStream中的一条记录就是一行文本,之后想将文本行分解成单词。
#将每行分解成为多个单词
words = lines.flatMap(lambda line:line.split(" "))
flatMap方法是一个一对多的DStream操作,从源DStream的每条记录生成多个新的纪录。这里每一行分解成多个单词,单词流数据表示为words 对象。接下来,需要计数单词出现的次数。
#计数每一批次的每个单词
pairs = words.map(lambda word:(word,1))
wordCount = pairs.reduceByKey(lambda x,y:x+y)
#打印每个RDD中的前10个元素
wordCount.pprint()
words对象通过一对一的map转换为(word,1)对DStream,之后通过reduce得到每个批次中单词出现的频率,最终wordCounts.pprint()打印每秒中计数情况。
到此为止还没真正执行流处理的进程,要开启所有的转换操作需要
ssc.start() #开启计算进程
ssc.awaitTermination() #等待计算终止
Spark Streaming例子的完整代码在NetworkWordCount.
如果已经下载并安装了Spark,就可以运行这个例子了。需要先运行Netcat来提供数据
$ nc -lk 9999
然后运行示例
$ ./bin/spark-submit examples/src/main/python/streaming/network_wordcount.py localhost 9999
打印结果
# TERMINAL 2: RUNNING network_wordcount.py
$ ./bin/spark-submit examples/src/main/python/streaming/network_wordcount.py localhost 9999
...
-------------------------------------------
Time: 2014-10-14 15:25:21
-------------------------------------------
(hello,1)
(world,1)
...
基本概念
接下来,揭示Spark Streaming基础概念
库依赖
类似于Spark,Spark Streaming也可以从Maven Central获取。要编写Spark Streaming 程序,需要将这个依赖加入到SBT或者Maven项目中
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.10</artifactId>
<version>1.3.1</version>
</dependency>
要接受来自Kafka,Flume,Kinesis这些不被包含在Spark Streaming核心API中的源,需要将对应的artifact spark-streaming-xyz_2.12加到依赖中,例如
Source | Artifact |
---|---|
Kafka | spark-streaming-kafka-0-10_2.12 |
Flume | spark-streaming-flume_2.12 |
Kinesis | spark-streaming-kinesis-asi_2.12[Amazon Software License] |
最先列表请访问Maven repository。
初始化StreamingContext
要初始化Spark Streaming程序,必须创建StreamingContext,它是Spark Streaming功能的核心
StreamingContext对象是由SparkContext对象创建而来的。
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
sc = SparkContext(master, appName)
ssc = StreamingContext(sc, 1)
appName参数代表在集群UI中展示的应用名称。master参数可以是Spark,Mesos或者YARN集群URL或者“local[*]”字符串来在本地运行。实际上真在集群中运行的时候不会写死master参数而是通过spark-submit启动参数来识别。这里就在本地运行了,直接用local[*]来运行Spark Streaming处理程序。
批次间隔时间符合应用程序的需求和集群情况。在性能调优细致讲解。
上下文定义之后必须:
- 通过创建输入DStream来定义数据源
- 通过在DStream上使用转换和输出操作处理流数据
- 通过streamingContext.start()来开始接收数据并处理
- 通过streamingContext.awaitTermination()来终止处理(手动终止或由于错误)
- 采用streamingContext.stop()来手动停止处理
注意:
- 一旦上下文开启,就不能有新的流计算操作加入进去
- 一旦上下文停止,就不能重启
- 一个JVM中一个时刻只能有一个StreamingContext运行
- stop()终止了StreamingContext也会终止SparkContext.要想终止前者而不终止后者需要在stop()设置stopSparkContext为false
- 一个SparkContext只要终止前一个StreamingContext就能用来复用创建下一个StreamingContext。
离散Streams(DStreams)
离散Stream或DStream是由Spark Streaming提供的基础抽象类。表示连续的数据流,来源于输入数据或对数据流进行转换操作而来。DStream内部由一系列RDD组成。一个DStream中的每个RDD包含某个时间段内的数据:
应用在DStream上的任何操作都会转为对内部RDD的操作。例如前面的将文本行数据流转换为单词数据流,flatMap操作就应用在lines DStream对象中的每个RDD从而生成了words DStream中的RDD。例如:
内部的RDD transformation由Spark 引擎执行。DStream操作隐藏了大部分细节,为开发者提供高层抽象。这些操作后面详细讨论。
输入DStreams和接收器
输入DStream表示由原始数据创建的DStream,在前面的例子中lines就是这样的。每一个输入DStream都和一个Receiver对象关联,它接收数据并将其存入Spark内存中。
Spark Streaming提供两类内置的流数据源:
- 基础数据源:直接从StreamingContext API可以得到的数据源对象。例如文件系统数据源,socket连接和Akka actors。
- 高级数据源:通过外部工具类获得的Kafka,Flume,Kinusis等。这要求依赖一节中那些额外的依赖。
接下来讨论这些数据源API。
如果你想并行接受多个数据源的数据,可以创建多个DStream。这会同时创建多个接收器来同时接受数据。注意Spark worker/executor是一个长期运行的任务,他需要占据分配给Spark Streaming应用的一个core。因此确保分配了足够的core或者线程来处理接受的数据。
记住:
- 当在本地运行Spark Streaming程序的时候,不要使用将local或者local[1]作为master URL。因为这都是意味着只有一个线程来运行本地任务。如果基于接收器来运行输入DStream,则该线程只能用来运行receiver,没有线程处理接受到的数据了。所以在本地运行时一直使用local[n],n>接收器的数量才能有线程处理数据。
- 集群中运行要保证分配的核心数大于接收器的数量,否则只能接受数据无法处理
基本数据源
入门实例中使用ssc.socketTextStream(…)来从TCP socket连接接收的数据上建立DStream。除了socket,StreamingContext API提供方法从文件系统输入源接收数据。
文件流
要从任意兼容HDFS的文件系统(HDFS,S3,NFS等)读取数据,一个DStream可以通过StreamingContext.fileStream[KeyClass,ValueClass,InputFormatClass]来创建。
文件流并不需要运行接收器因为不需要分配任何core来接收文件数据。
对于简单的文本文件,最简单的方法是StreamingContext.textFileStream(dataDirectory).
Python API不支持fileStream,只有textFileStream可以使用
streamingContext.textFileStream(dataDirectory)
如何监控目录
Spark Streaming监控dataDirectory目录并且处理该目录中的任何文件:
- 简单的目录"hdfs://namenode:8040/logs"会被监控,这个目录中的一级文件都可以处理。
- 现在也支持像"hdfs://namenode:8040/logs/2017/*"这样的POSIX glob模式。DStream会包含匹配该模式的目录中的所有文件。也就是说这是目录的pattern不是文件的啊。
- 所有文件必须是相同格式
- 所有文件必须有修改时间而非创建时间
- 一旦开始处理那么在当前窗口期内对该文件所做的改变不会导致重读,也就是忽略文件更新。
- 目录中目录越多,扫描文件更新的时间越长–虽然可能没啥更新
- 如果使用通配符定义目录,例如"hdfs://namenode:8040/logs/2016-*"那么将一个目录重命名匹配上了该格式则该目录也会被监控。只有当该目录修改时间在当前窗口期之内,该目录中的文件才会包含在流中。
- 调用FileSystem.setTimes()来修改文件时间戳可以使该文件包含在后期的窗口期内,即使文件没啥改变这也行啊
使用对象存储作为数据源
像HDFS这样的文件系统只要输出流创建了那么就会设置文件的修改时