Spark Streaming详解----概述、基本概念、性能调优

本文章主要讲述SparkStreaming概念原理、基本概念、以及调优等一些知识点。

1      概述

1.1  SparkStreaming是什么

Spark Streaming 是个批处理的流式(实时)计算框架。其基本原理是把输入数据以某一时间间隔批量的处理,当批处理间隔缩短到秒级时,便可以用于处理实时数据流。

   支持从多种数据源获取数据,包括Kafk、Flume、Twitter、ZeroMQ、Kinesis以及TCP sockets,从数据源获取数据之后,可以使用诸如map、reduce、join等高级函数进行复杂算法的处理。最后还可以将处理结果存储到文件系统,数据库等。

Spark Streaming处理的数据流图:





以上的连续4个图,分别对应以下4个段落的描述:

  • Spark Streaming接收Kafka、Flume、HDFS和Kinesis等各种来源的实时输入数据,进行处理后,处理结果保存在HDFS、Databases等各种地方。
  • Spark Streaming接收这些实时输入数据流,会将它们按批次划分,然后交给Spark引擎处理,生成按照批次划分的结果流。
  • Spark Streaming提供了表示连续数据流的、高度抽象的被称为离散流的DStream。DStream本质上表示RDD的序列。任何对DStream的操作都会转变为对底层RDD的操作。
  • Spark Streaming使用数据源产生的数据流创建DStream,也可以在已有的DStream上使用一些操作来创建新的DStream。

1.2    2. Spark Streaming能做什么

目前而言SparkStreaming 主要支持以下三种业务场景

  • 无状态操作:只关注当前批次中的实时数据,例如:

  1. 商机标题分类,分类http请求端 -> kafka -> Spark Streaming -> http请求端Map -> 响应结果
  2. 网库Nginx访问日志收集,flume->kafka -> Spark Streaming -> hive/hdfs
  3. 数据同步,网库主站数据通过“主站”->kafka->Spark Streaming -> hive/hdfs
  • 有状态操作:对有状态的DStream进行操作时,需要依赖之前的数据 除了当前新生成的小批次数据,但还需要用到以前所生成的所有的历史数据。新生成的数据与历史数据合并成一份流水表的全量数据例如:
  1. 实时统计网库各个站点总的访问量
  2. 实时统计网库每个商品的总浏览量,交易量,交易额。
  • 窗口操作:定时对指定时间段范围内的DStream数据进行操作,例如:
  1.  网库主站的恶意访问、爬虫,每10分钟统计30分钟内访问次数最多的用户。

1.3        特性

1.3.1       优点:

  • 吞吐量大、速度快。
  • 容错:SparkStreaming在没有额外代码和配置的情况下可以恢复丢失的工作。checkpoint。
  • 社区活跃度高。生态圈强大。
  • 数据源广泛。

1.3.2  缺点:

  •  延迟。500毫秒已经被广泛认为是最小批次大小,这个相对storm来说,还是大很多。所以实际场景中应注意该问题,就像标题分类场景,设定的0.5s一批次,加上处理时间,分类接口会占用1s的响应时间。实时要求高的可选择使用其他框架。

2   基础概念-开发

2.1  简单示例

2.1.1  Word count词频计算demo

[java]  view plain  copy
  1. object NetworkWordCount {  
  2.   def main(args: Array[String]) {  
  3.     val sparkConf = new SparkConf()  
  4.     val ssc = new StreamingContext(sparkConf, Seconds(1))  
  5.     val lines = ssc.socketTextStream(hostname, port)  
  6.     val words = lines.flatMap(_.split(" "))  
  7.     val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _)  
  8.     wordCounts.print()  
  9.     ssc.start()  
  10.     ssc.awaitTermination()  
  11.   }  

2.1.2  说明

1.  通过创建输入DStreams来定义输入源。

2.  通过将转换和输出操作应用于DStream来定义流式计算。

3.  开始接收数据并使用它进行处理streamingContext.start()

4.  等待处理停止(手动或由于任何错误)使用streamingContext.awaitTermination()

5.  可以手动停止处理streamingContext.stop()

2.1.3  注意

1.  一旦上下文开始,就不能设置或添加新的流计算。

2.  一旦上下文停止,它将无法重新启动。

3.  只有一个StreamingContext可以在JVM中同时处于活动状态。

2.2  输入源

Spark Streaming提供了两类输入源。

·        基本来源:StreamingContextAPI中直接提供的资源。示例:文件系统,套接字连接。

1.文件系统:streamingContext.fileStream(hdfsDataDirectory)

SparkStreaming将监听目录dataDirectory并处理在该目录中创建的任何文件(不支持嵌套目录中写入的文件)
文件必须具有相同的数据格式。
必须dataDirectory通过将数据原子移动重命名为数据目录来创建文件。
移动后,文件不能更改。因为,如果文件被不断附加,则不会读取新的数据。


2.套接字连接:streamingContext.socketTextStream(hostname, port)

  Sparkstreaming监听对应主机-端口,处理发送到该端口的数据。

·        高级来源:Kafka,Flume等资源可以通过额外的实用类来获得。

      实际应用场景中,Kafak使用较多,主要介绍Kafka的使用:

KafkaUtils.createStream(ssc, zkQuorum, groupId, topicsMap)

sscstreamingContext

zkQuorum:kafka元数据在zookeeper中的存储地址(示例:node1:2181/kafka)

groupId:spark streaming接受kafka数据使用的用户组id,可通过该参数控制每次接受kafka数据的索引位置,spark streaming每次启动都会从该groupId上次接收到的数据位置开始接收。

topicsMap:Map[String, Int]类型对象,key对应接收的数据 topic名称,value为线程数量。sparkstreaming接收kafka数据的启动的线程数量,即并发量

 如果要在流式应用程序中并行接收多个数据流,则可以创建多个输入DStream

 

2.3  DStream转换操作

操作 含义
map(func)

通过传递源DStream的每个元素通过函数func返回一个新的DStream

flatMap(func) map类似,但每个输入项可以映射到0个或更多的输出项。
filter(func) 通过仅选择func返回true 的源DStream的记录来返回新的DStream 
repartition(numPartitions) 通过创建更多或更少的分区来更改此DStream中的并行级别。
union(otherStream) 返回一个新的DStream,它包含源DStream和otherDStream中元素的并集。
count() 通过计算源DStream的每个RDD中的元素数量来返回单元素RDD的新DStream
reduce(func) 通过使用函数func(它需要两个参数并返回一个),通过聚合源DStream的每个RDD中的元素来返回单元素RDD的新DStream 。该函数应该是关联的,以便可以并行计算。
countByValue() 当调用类型为K的元素的DStream时,返回一个新的DStreamKLong)对,其中每个键的值是源DStream的每个RDD中的频率。
reduceByKey(func,[numTasks]) 当(KV)对的DStream被调用时,返回(KV)对的新DStream,其中使用给定的reduce函数聚合每个键的值。注意:默认情况下,使用Spark的默认并行任务数(2为本地模式,群集模式中的数字由config属性决定spark.default.parallelism)进行分组。您可以传递可选numTasks参数来设置不同数量的任务。
join(otherStream,[numTasks]) 当(KV)和(KW)对的两个DStream被调用时,返回一个新的(K,(VW))对的DStream与每个键的所有元素对。
cogroup(otherStream,[numTasks]) 当调用(KV)和(KW)对的DStream时,返回一个新的DStreamKSeq [V]Seq [W])元组。
transform(func) 通过对源DStream的每个RDD应用RDDRDD函数来返回一个新的DStream。这可以用于对DStream进行任意RDD操作。
updateStateByKey(func) 返回一个新的“状态”DStream,其中通过对键的先前状态应用给定的功能和键的新值来更新每个键的状态。这可以用于维护每个密钥的任意状态数据。

Transform操作: 

[java]  view plain  copy
  1. val spamInfoRDD = ssc.sparkContext.newAPIHadoopRDD(...)  
  2. val cleanedDStream = wordCounts.transform(rdd => {  
  3.   rdd.join(spamInfoRDD).filter(...)  ...  
  4. })  

UpdateStateByKey 操作:

[java]  view plain  copy
  1.     val lines = KafkaUtils.createStream(ssc, zkQuorum, group, topicMap).map(_._2)  
  2.     //产生我们需要的pair rdd  
  3.     val linerdd = lines.map{row =>{  
  4.      ···  
  5.       (key, amt)  
  6. }}  
  7.   
  8. val addFunc = (currValues: Seq[Int], preValueState: Option[Int]) =>{  
  9.       //通过spark内部的reducebykey按key规约,然后这里传入某key当前批次的seq,再计算key的总和  
  10.       val currentCount = currValues.sum  
  11.       //已经累加的值  
  12.       val previousCount = preValueState.getOrElse(0)  
  13.       //返回累加后的结果,是一个Option[Int]类型  
  14.       Some(currentCount + previousCount)  
  15.     }  
  16.     linerdd.updateStateByKey[Int](addFunc _).print()  

Windows操作

下图说明了这个窗口。

如图:

1. 红色的矩形就是一个窗口,窗口hold的是一段时间内的数据流。

2.这里面每一个time都是时间单元,在官方的例子中,每隔window size是3 time unit, 而且每隔2个单位时间,窗口会slide一次。

所以基于窗口的操作,需要指定2个参数:

· window length - The duration of the window (3 inthe figure)

· slide interval - The interval at which the window-basedoperation is performed (2 in the figure).  

举个例子吧:

还是以wordcount举例,每隔10秒,统计一下过去30秒过来的数据。

val windowedWordCounts = pairs.reduceByKeyAndWindow(_ + _, Seconds(30), Seconds(10))  

这里的paris就是一个DStream,每条数据类似(word,1)

一些常见的窗口操作如下。所有这些操作都采用上述两个参数 - windowLengthslideInterval

操作

含义

windowwindowLengthslideInterval

返回基于源DStream的窗口批次计算的新DStream。

countByWindowwindowLengthslideInterval

返回流中元素的滑动窗口数。

reduceByWindowfuncwindowLengthslideInterval

返回一个新的单元素流,通过使用func在滑动间隔中通过在流中聚合元素创建。

reduceByKeyAndWindowfuncwindowLengthslideInterval,[ numTasks ])

当对(K,V)对的DStream进行调用时,返回(K,V)对的新DStream,其中每个键的值 在滑动窗口中使用给定的减少函数func进行聚合

countByValueAndWindowwindowLength, slideInterval,[numTasks ])

当调用(K,V)对的DStream时,返回(K,Long)对的新DStream,其中每个键的值是其滑动窗口内的频率。

 

2.4 DStream的输出操作

输出操作允许将DStream的数据推送到外部系统,如数据库或文件系统。由于输出操作实际上允许外部系统使用变换后的数据,所以它们触发所有DStream变换的实际执行(类似于RDD的动作)。目前,定义了以下输出操作:

操作

含义

print()

在运行流应用程序的驱动程序节点上的DStream中打印每批数据的前十个元素。

saveAsTextFilesprefix,[ suffix ])

将此DStream的内容另存为文本文件。基于产生在每批间隔的文件名的前缀后缀“前缀TIME_IN_MS [.suffix]”

saveAsObjectFilesprefix,[ suffix ])

将DStream的内容保存为SequenceFiles序列化的Java对象。基于产生在每批间隔的文件名的前缀后缀“前缀TIME_IN_MS [.suffix]”

saveAsHadoopFilesprefix,[ suffix])

将此DStream的内容另存为Hadoop文件。基于产生在每批间隔的文件名的前缀后缀前缀TIME_IN_MS [.suffix]”。 

foreachRDDfunc

对从流中生成的每个RDD 应用函数func的最通用的输出运算符。此功能应将每个RDD中的数据推送到外部系统,例如将RDD保存到文件,或将其通过网络写入数据库。请注意,函数func在运行流应用程序的驱动程序进程中执行,通常会在其中具有RDD动作,从而强制流式传输RDD的计算。

注意

  • DStreams通过输出操作进行延迟执行,就像RDD由RDD操作懒惰地执行。具体来说,DStream输出操作中的RDD动作强制处理接收到的数据。因此,如果您的应用程序没有任何输出操作,或者具有输出操作,比如dstream.foreachRDD()没有任何RDD操作,那么任何操作都不会被执行。系统将简单地接收数据并将其丢弃。
  • 默认情况下,输出操作是一次一个执行的。它们按照它们在应用程序中定义的顺序执行。

2.5 DataFrame和SQL操作

可以轻松地在流数据上使用DataFrames和SQL操作。您必须使用StreamingContext正在使用的SparkContext创建一个SparkSession。此外,必须这样做,以便可以在驱动程序故障时重新启动。

这在下面的示例中显示。将每个RDD转换为DataFrame,注册为临时表,然后使用SQL进行查询。

[html]  view plain  copy
  1. val words: DStream[String] = ...  
  2. words.foreachRDD { rdd =>  
  3.   // Get the singleton instance of SparkSession  
  4.   val spark = SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()  
  5.   import spark.implicits._  
  6.   
  7.   // Convert RDD[String] to DataFrame  
  8.   val wordsDataFrame = rdd.toDF("word")  
  9.   
  10.   // Create a temporary view  
  11.   wordsDataFrame.createOrReplaceTempView("words")  
  12.   
  13.   // Do word count on DataFrame using SQL and print it  
  14.   val wordCountsDataFrame =   
  15.     spark.sql("select word, count(*) as total from words group by word")  
  16.   wordCountsDataFrame.show()  
  17. }  

2.6  MLlib操作

可以通过训练出个模型,然后将模型作为广播变量,在DStream操作中使用该模型预测相关数据。

2.7 缓存/持久性

与RDD类似,DStreams还允许开发人员将流的数据保留在内存中。也就是说,使用persist()DStream上的方法将自动将该DStream的每个RDD保留在内存中。如果DStream中的数据将被多次计算(例如,相同数据上的多个操作),这是非常有用的。对于基于窗口的操作,像reduceByWindowreduceByKeyAndWindow和基于状态的操作一样updateStateByKey,这是隐含的。因此,基于窗口的操作生成的DStreams将自动保留在内存中,无需开发人员的调用persist()

2.8  CheckPoint/检查点

流式应用程序必须全天候运行,因此必须能够适应与应用程序逻辑无关的故障(例如,系统故障,JVM崩溃等)。为了可以这样做,Spark Streaming需要检查足够的信息到容错存储系统,以便可以从故障中恢复。

2.8.1  如何使用CheckPoint

启用 checkpoint,需要设置一个支持容错的、可靠的文件系统(如 HDFS、s3 等)目录来保存 checkpoint 数据。通过调用 streamingContext.checkpoint(checkpointDirectory) 来完成。另外,如果你想让你的application能从 driver 失败中恢复,你的application 要满足:

·        若 application为首次重启,将创建一个新的 StreamContext 实例

·        如果 application是从失败中重启,将会从 checkpoint 目录导入 checkpoint 数据来重新创建 StreamingContext 实例

通过 StreamingContext.getOrCreate 可以达到目的:

[java]  view plain  copy
  1. def functionToCreateContext(): StreamingContext = {  
  2.     val ssc = new StreamingContext(...)   // new context  
  3.     val lines = ssc.socketTextStream(...) // create DStreams  
  4.     ...  
  5.     ssc.checkpoint(checkpointDirectory)   // set checkpoint directory  
  6.     ssc  
  7. }  
  8. val context = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext _)  
  9.   
  10. ```  
  11. context.start()  
  12. context.awaitTermination()  

2.9  Spark Streaming程序提交

与spark提交方式一样

spark-submit

 --class:您的应用程序的入口点(例如org.apache.spark.examples.SparkPi

         --master:集群的主URL(例如spark://10.2.9.114:7077;提交到yarn集群写:yarn

         --deploy-mode:是否将驱动程序部署在工作节点(cluster)或本地作为外部客户端(client)(默认值:client

         --conf:任意Spark配置属性,key = value格式。对于包含空格的值,用引号括起“key = value”(如图所示)。

         application-jar:包含应用程序和所有依赖关系的捆绑jar的路径。该URL必须在集群内全局可见,例如所有节点上存在的hdfs://路径或file://路径。

         application-arguments:参数传递给主类的main方法,如果有的话。

提交到Yarn集群的特殊参数:

--executor-memory    每个executor内存大小

--num-executors     executor数量

--executor-cores     executor cpu数量

3   性能调优                                       

3.1  合理设置批处理                                           

1.    通过有效利用集群资源减少每批数据的处理时间。

2.    设置正确的批量大小,使得批量的数据可以像接收到的那样快速处理(即数据处理与数据摄取保持一致)。

 

3.2  数据接收中的并行级别

[java]  view plain  copy
  1. val numStreams = 5  
  2. val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) }  
  3. val unifiedStream = streamingContext.union(kafkaStreams)  
  4. unifiedStream.print()  

3.3  内存优化

3.3.1  数据序列化

可以通过调优序列化格式来减少数据串行化的开销

 

3.3.2  对象类型

例如HashMapLinkedList等一些结构占用空间较大,可考虑优化使用对象类型。

3.4  广播大变量

使用广播功能可以大大减少群集上启动作业的成本。

4   FAQ

4.1  executor间task分布不均匀

task大都集中在特定的少数executor上执行,并行度不够。

原因:

这些点为receiver所在节点。Receiver会将接收到的数据的第一个副本放在本地,另外的副本随机分布在其他节点。党我们只设置一个副本时(e.g. MEMORY_ONLY_SER),数据会全部集中在receiver所在的几个节点,task也会被优先分发到这些点上的executor中执行。

4.2  spark Streaming任务失败

原因:Spark Streaming存在执行一定时间后失败的问题

解决办法:定时重启Spark Streaming任务

 

未完待续···        

猜你喜欢

转载自blog.csdn.net/zhou_shaowei/article/details/80089207
今日推荐