本人刚开始入门学习Spark,打算先将Spark文档看一遍,顺便做点笔记,就进行一些翻译和记录。由于本人只会python,所以翻译都是以python部分代码进行。以下并非完全100%官网翻译,更多是个人理解+笔记+部分个人认为重要的内容的翻译,新手作品,请各位大神多多指正。
原文地址:http://spark.apache.org/docs/latest/streaming-programming-guide.html
Overview
Spark Streaming是核心Spark API对于实时数据流处理的扩展功能。数据可以从kafka,flume,kinesis或者tcp socket导入,可以通过map,reduce,join,window等高级函数进行处理。处理好的结果能够保存到文件系统,数据库或者在仪表盘进行实时展示。
工作流程如下图。Spark Streaming接收到实时输入数据,并且将数据分割为批(batches),由Spark引擎对其进行处理后,生成最终的流结果集(以批为单位)
Spark Streaming提供了高层次抽象discretized stream 或 DStream,其表示了连续的流数据。Dstream可以通过kafka,flume,kinesis或者由其他Dstream为创建源。内部来说,Dstream实际为RDD组成的序列。
A Quick Example
以统计从tcp端口接收到的文字数量为例:
SparkContext是所有流功能的入口点
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
# 创建一个有2个工作线程的 StreamingContext 对象,批间隔为1s
sc = SparkContext("local[2]", "NetworkWordCount")
ssc = StreamingContext(sc, 1)
通过这个context创建一个Dstream
# Create a DStream that will connect to hostname:port, like localhost:9999
lines = ssc.socketTextStream("localhost", 9999)
lines表示将从tcp获取的数据流,
# Split each line into words
words = lines.flatMap(lambda line: line.split(" "))
flatmap 是1对多的Dstream操作,其创建了一个新的Dstream。在这个例子中,每一行会被分割成多个单词,新的Dstream即为单词流。
# Count each word in each batch
pairs = words.map(lambda word: (word, 1))
wordCounts = pairs.reduceByKey(lambda x, y: x + y)
# Print the first ten elements of each RDD generated in this DStream to the console
wordCounts.pprint()
单词DStream被1对1映射为(word,1)键值对,然后由ReduceByKey处理。最后由ppring()打印每秒生成的结果。
注意上述并没有实际执行计算,如果执行计算和处理,就得调用下面语句
ssc.start() # Start the computation
ssc.awaitTermination() # Wait for the computation to terminate
另外还需要执行
nc -lk 9999
Basic Concepts
Linking
与Spark类似,Spark Streaming可以通过Maven来构建。但需要添加相应依赖。对于 Kafka, Flume, 和 Kinesis等在Core APi中没有原生支持的数据源,需要将spark-streaming-xyz_2.11添加到依赖中。
Initializing StreamingContext
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
sc = SparkContext(master, appName)
ssc = StreamingContext(sc, 1)
- 首先要创建SparkContext对象,然后创建StreamingContext对象。
- 定义数据源,创建Dstream
- 通过转换和输出操作定义数据源计算过程
- 通过streamingContext.awaitTermination()等待过程停止,也可以通过streamingContext.stop()停止
需要注意的点:
- 一旦context开始载入工作,就不能新增其他流计算
- 一旦context停止工作,就不能重新启动
- 在同一时间,有且只有一个StreamingContext在一个JVM中激活
- stop() 将停止StreamingContext和SparkContext,如果只想要停止StreamingContext,可以再 stop()中设置参数stopSparkContext为falese
- SparkContext可以通过创建多个StreamingContexts被重复利用,条件是上一个StreamingContext已停止。
Discretized Streams (DStreams)
DStream是有Spark Streaming的基本抽象对象,其代表了连续的数据流。内部DStream是有一系列连续的RDDs组成,每个RDD都包含着一定时间间隔中的数据。
所有应用到Dstream上的操作最终应用到的是RDDs。在上面例子中,flatmap被应用到每个RDD中,再形成了单词Dstream的RDDs。
Input DStreams and Receivers
输入DStream表示的是输入数据流,在例子中,输入流是来自netcat服务器的数据。每一个输入DStream(除了文件流)都必须有对应的receiver对象,它的作用是从数据源接受数据并且储存在Spark的内存中等待处理。
有两种内置的数据源类型
- 基本类型/Basic Sources:对于StreamingContext API直接可用的数据源,例如文件系统,socket连接。
- 高级类型/Advanced Sources:kafka,flume,kinesis等数据源,通过附加的依赖可被输入到Spark中
如果需要同时接受多个数据流,可以创建多个输入DStream,这就意味着需要创建多个receiver。值得注意的是Spark worker/executor是一个长期运行的任务,所以其将占据其中一个分配给Spark Streaming的cpu核心。所以流处理应用必须要有足够核心(本地的话也可以是线程)来处理接受和数据和运行receiver。
需要注意的点:
本地运行spark流处理,不要用’local’或者’local[1]’,上述意思是只分配一个线程。那么这个线程可能会被receiver占用,那么就没有线程来对数据进行处理。所以本地运行n必须大于receiver的数据。
将上述逻辑扩展到集群,分配给流处理的核心数量必须大于receivers的数量。
Basic Sources
除了上述例子的socket连接,文件也属于基本类型。
File Streams
可以通过StreamingContext.fileStream[KeyClass, ValueClass, InputFormatClass]来创建DStream来读取和HDFS API兼容的任何文件系统文件(例如HDFS,S3,NFS等)
文件流不需要运行receiver
python api不支持文件流,只支持文档流
streamingContext.textFileStream(dataDirectory)
How Directories are Monitored
Spark流将监控指定的文件夹并处理其中的所有文件。
- 可以监控简单的文件夹,例如’hdfs://namenode:8040/logs/’
- 可以使用通配符,例如’hdfs://namenode:8040/logs/2017/*’
- 文件数据格式必须一致
- 文件是基于修改时间而非创建时间来评判是否需要处理
- 一旦文件被处理过,即使在当前window的文件的修改也不会触发再次读取处理。
- 文件夹文件越多,就需要更长时间来扫描更改,即使没有文件被更改也是如此。
- 如果采用通配符,例如’hdfs://namenode:8040/logs/2016-*’,符合路径的整个文件夹也会被纳入监控。但只有修改时间在当前window中的文件会被处理。
- 调用FileSystem.setTimes()来修改时间是让window重新处理文件的办法,即使该文件没有被修改。
Using Object Stores as a source of data
完整的文件系统例如HDFS趋向于输出流被创建以后马上设置文件的修改时间。即使文件被打开,特别是更新还没有完成的情况下,这部分内容可能会被忽略。
为了保证修改能够被window正确读取,可以将文件拷贝到非监控目录,在输出流关闭后马上将文件mv到原目录。
与此相反,例如S3等对象存储的mv操作非常慢,因为数据是真实发生拷贝移动的。而且,mv对象会将mv操作时间作为其修改时间,所以也可能不会符合window的范围。
Streams based on Custom Receivers
用户可以自定义receiver来创建DStream
Queue of RDDs as a Stream
如果用于测试SPark流处理,可以通过streamingContext.queueStream创建队列RDD,每个队列中的RDD将会被当成DStream中的一个批单位处理。
Advanced Sources
这类数据源需要和对接外部非Spark库,某些库需要复杂的依赖才能运行。因此,为了减少复杂度,从这些源创建DStream的功能被分割到独立的库中,可按需连接。
上述数据源在SPark shell中是不可用的,因为在shell中无法测试基于上述数据源的应用。如果想要在spark shell中运行,可以下载Maven的jar和依赖。
Custom Sources
该部分python api不支持。
输入流可以自定义,只需要自己定义一个receiver即可。
Receiver Reliability
kafka和flume支持ack确认
Reliable Receiver - 当数据被接受并且在spark中以多副本存放后,receiver会发送ack到数据源
Unreliable Receiver - receiver不发送ack到数据源。用于不支持ack确认的数据源,或者不需要ack确认增加复杂性的场合。
Transformations on DStreams
转换 | 用法 |
---|---|
map(func) | 将函数func作用在DStream每个元素上,并返回一个新的DStream |
flatMap(func) | 类似于map,但每个元素可以映射到0-N个输出中。 |
filter(func) | 将函数func作用在每个元素上,只返回由func判断后返回ture的那些元素组成的DStream |
repartition(numPartitions) | 通过调整分区数量改变DStream的处理并行度 |
union(otherStream) | 返回组合源DStream和其他DStream的组合而成的新的DStream |
count() | 返回一个包含多个单元素RDD的DStream,RDD中包含了每个源RDD中元素数量的个数 |
reduce(func) | 通过func(接受2个参数并返回1个值)对源DStream每个RDD元素的聚合,返回一个包含多个单元素RDD的DStream。函数应该具备无序性和相关性 |
countByValue() | 作用在DStream的元素上,返回一个类型为(K,long)的键值对,其中值为源DStream中每个RDD的key出现过的次数。 |
reduceByKey(func, [numTasks]) | 作用在类型为(K,V)的DStream上,返回类型为(K,V)的新的DStream,值为通过func对每个key的聚合以后的值。可以通过numTasks制定任务数量 |
join(otherStream, [numTasks]) | 作用在两个类型为(K, V) 和 (K, W)的DStream的键值对上,返回类型为(K, (V, W))的键值对。 |
cogroup(otherStream, [numTasks]) | 作用在(K, V) 和 (K, W)的DStream键值对上,返回一个以元组(K, Seq[V], Seq[W])类型的DStream。 |
transform(func) | 函数将作用在源DStream的每个RDD上,并返回一个新的DStream,这样就可以在DStream上应用RDD的全部类型操作 |
updateStateByKey(func) | 返回一个新’状态‘的RDD,通过函数对每个key的状态和值进行更新。 |
UpdateStateByKey Operation
updateStateByKey可以在持续计算结果更新中维护一个状态以及值。
- 定制状态,状态可以为任意数据类型
- 定义状态更新函数,定义函数如何更新当前状态和从输入流计算新的值。
对于每个batch单位,spark会将func应用到所有已存在的键值对上,而不理会是否为新数据。如果func返回None则k-v对将被清除(有误,例子并非如此)
例如想要持续看到输入流中每个词的个数,这里running count就是状态,值为一个整数。
def updateFunction(newValues, runningCount):
if runningCount is None:
runningCount = 0
return sum(newValues, runningCount) # add the new values with the previous running count to get the new count
runningCounts = pairs.updateStateByKey(updateFunction)
func将对每个词调用,每秒更新1次newValue值到runningcount中。
updateStateByKey需要配置checkpoint目录,例如ssc.checkpoint(‘/temp’)
Transform Operation
转换操作(包括其衍生操作transformWith)能够将任意将RDD-RDD函数应用到DStream中,这就可以将任何没有在DStream API直接支持的RDD操作应用到DStream中。例如将每个batch与另一个数据集进行join操作并没有在DStream直接支持,通过Transform操作后即可利用RDD的join实现。例如,可以通过将spam信息实时join到输入数据流中实现实时数据清理。
spamInfoRDD = sc.pickleFile(...) # RDD containing spam information
# join data stream with spam information to do data cleaning
cleanedDStream = wordCounts.transform(lambda rdd: rdd.join(spamInfoRDD).filter(...))
注意是在每个batch中应用该函数,所以实现可以随着时间改变的RDD操作,例如分区数更改,广播变量等,可以随着batch不同而不同。