1 Spark Streaming本质


主要内容:

1.      Spark Streaming 另类在线实验

2.      理解Spark Streaming本质

 

写在前面的话:

         为什么我们要以SparkStreaming为切入点进行Spark的源码定制呢?

         原因如下:

1从研究目的来看

在Spark创立之初,并没有现在我们常用的这些子框架,如Spark Streaming、Spark SQL、Spark R等内容,仅有原始的Spark core;而这些子框架是在Spark不断发展中加入到Spark大家庭的。我们采用的是与Spark core联系最紧密的Spark Streaming作为切入点,可以透过一个子框架的彻底研究,从而了解Spark的核心问题与解决办法

 

2各个框架的对比来说

         现阶段,我们运行最多的是Sparkcore 以及其他的一些常用子框架。一下我们做了一些纵向对比。

 

框架

特点

备注

Spark SQL

基于Spark的快速的查询引擎

由于太多的SQL语句解析,会对我们研究的核心问题Spark运行到来不必要的干扰,不作为研究对象

Spark GraphX

基于Spark高效的图和图并行计算

近几个版本更新较少,难以体现最新的Spark发张方向,故不作为研究对象

Spark MLlib

封装了Vector与Matrix基础上结合RDD构建的机器学习库

由于其中涉及了许多算法,并不是我们研究Spark的重点,所以我们不研究这个框架。

Spark Streaming

面向流式处理的Spark子框架,与Spark core联系最紧密,最想Spark core上的一个应用程序

符合我们通过子框架达到透视Spark的要求。因此选择这个子框架作为我们研究的对象。

 

Spark Streaming是一个流式处理技术,而当前对于数据处理的要求基本上都要,数据都是流式数据且是实时在线处理的。而从我对大数据的初步印象来讲,也是关注与流式的数据即将大量数据流入到机器/处理器,并能立刻产生反馈的结果。此外Spark Streaming还可以利用Spark在图计算和机器学习以及Spark SQL、Spark R上获得成果,来进行实时在线数据处理。这要得益于在Spark技术堆栈中一体化结构的优势。这一优势带来的直接结果就是Spark Streaming在调用其他Spark 技术时,无需进行设置,直接调用。我们可以预想在未来Spark各个子框架协作,必将对业界产生巨大影响。从而Spark Streaming会成为未来的企业统一选择的技术。

3.从Spark Streaming与其他框架联系来看

  在SparkStreaming的背后会与Spark其他子框架,有很大的关联性,我们在Spark Streaming中可以使用其他子框架,并进行整合,从而可以从中展现其他子框架的风貌,进而展现这个大数据技术的缩影。

4.Spark调试来看

  在基于Spark框架的代码中,基于Spark Streaming的程序是最容易出现问题的,其根本原因在于数据是流动的,因此框架需要控制数据的流入、作业的划分、数据处理;考虑的方面多,相应的问题也变多,但是也最能体现编程者的能力,最吸引人。

5.从Spark Streaming的运行来说

         它更像是在Sparkcore之上的一个应用程序,需考虑程序输入的数据是变化的和数据怎么处理两方面。

6.Spark Streaming的地位

         可以说SparkStreaming是Spark框架中的一个关键点,要想成为高手,Spark Streaming是最能修炼Spark 内功的地方,也是提升我们的最佳的试炼场。

所以,我们以Spark Streaming作为切入点,来进行源码定制。

首先,我们进行一个实验:

         这里我们有一个来自DT大数据梦工厂IMF课程的案例------在线过滤黑名单

源代码如下:

package com.dt.spark.Streaming

import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkContext, SparkConf}

/**
  * 使用Scala开发集群运行的Spark 在线黑名单过滤程序
  * @author DT大数据梦工厂
  * 新浪微博:http://weibo.com/ilovepains/
  * Created by pc-Hipparic on 2016/5/9.
  *
  * 背景描述:在广告点击计费系统中,我们在线过滤掉黑名单的点击,
  * 进而保护广告商的利益,只进行有效的广告点击计费;
  * 或者在防刷评分(或者流量)系统。(可以通过ip查看刷频分或流量的ip)过滤掉
  * 无效的投票或者频分或者流量;
  * 黑名单:
  * 实现技术:利用黑名单库(这里只是利用集合来模拟),通过对每个batch中的RDD进行操作,
  *   这是就要利用transform Api直接基于RDD编程,进行join操作(若要考虑性能则需考虑广播)
  */
object onlineBlackListFilter {
  def main(args: Array[String]) {
    /**
      * 第一步:创建Spark的配置对象SparkConf,设置Spark程序的运行时的配置信息,
      * 例如说通过setMaster来设置程序要连接的Spark集群的Master和URL,如果设置为
      * local,则代表Spark程序在本地运行,特别适合机器配置条件差的情况。
      */
    val conf = new SparkConf()  //创建SparkConf对象
    conf.setAppName("OnlineBlackListFilter") //设置应用程序名称
    conf.setMaster("Spark://Master:7077")    //设置集群运行下的Master

    /**
      * 第二步:创建SparkStreaming上下文
      */
    val  ssc = new StreamingContext(conf,Seconds(300))  //我们将Batch interval由原来的30s放大到300s

    /**
      * 第三步:创建黑名单
      * 黑名单数据准备,实际上黑名单一般都是动态生成的,例如在Redis或者其他DB中,
      * 黑名单的生成往往有复杂的业务逻辑,视具体情况而言有不同的算法。
      * 在Spark Streaming进行处理时每次都能够访问完整的信息。
      */
    val blackList = Array(("hadoop",true),("mahout",true))
    val blackListRDD = ssc.sparkContext.parallelize(blackList,8)
    //考虑如何利用kafka方式?
    val adsClickStream = ssc.socketTextStream("Master",9999)

    /**
      * 备注:此处模拟的广告点击的每条数据的格式为:time、name
      * 此处map操作的结果是name、(time、name)的格式
      */
    val adsClickStreamFormatted = adsClickStream.map(ads => (ads.split(" ")(1),ads))
    adsClickStreamFormatted.transform(userClickRDD => {
      //通过leftOuterJoin操作既保留了左侧用户广告点击内容的RDD的所有内容,又获得了相应
      //点击内容是否在黑名单中。
     val joinedBlackListRDD = userClickRDD.leftOuterJoin(blackListRDD)

      /**
        * 第四步:进行过滤
        * 进行filter过滤的时候,其输入元素是一个Tuple:(name,((time,name),boolean))
        * 其中第一个元素是黑名单的名称,第二个元素的第二个元素是进行leftOuterJoin的时候是否
        * 存在该值,如果存在的话,说明当前广告点击是黑名单,需要过滤掉,否则的话是优先点击内容。
        */
      val validClicked = joinedBlackListRDD.filter(joinedItem => {
        if(joinedItem._2._2.getOrElse(false)){
            false
          }else{
          true
        }
      })
      validClicked.map(validClick => {
        validClick._2._1
      })
    }).print

    /**
      * 第五步:数据输出
      * 计算后的数据一般都会写入kafka中,下游的计费系统会从kafka中pull到
      * 有效数据进行计费
      */
    ssc.start()
    ssc.awaitTermination()
  }
}

执行结果:


 

这里我们将Spark Streaming的Batch interval放大,主要是想通过Batch interval放大后更好的观察Spark Streaming的运行流程。此时由于我们将Batch interval放大很大,可以在Spark Streaming运行玩一次Batch后停掉它,来观察一次Batch,Spark Streaming是怎么运行的。

         首先,我们来看看WebUI

 

         可以发现,虽然只有一个Batch进行了处理但是却产生了5个Job。为什么?

为了解决这个问题,我们来看看每个Job的内部吧,对于Job 0:

它的DAG与我们在Spark Streaming的业务逻辑类即onlineBlackListFilter中的逻辑是不同的,我们没有写reduceByKey函数。所以,我们可以得到一个解释就是这是Spark Streaming框架帮我们启动的Job,同时也说明Spark Streaming在启动时会启动一些Job来实现一些流式处理所需的功能。(类似我们启动word时,它需要启动一些Job来实现监控,显示,临时文件保存等一些功能,不单单是仅仅启动我们写文档这个作业。)

对于Job 1:

它只有一个Stage,里面包含了对一个对RDD的操作。其实这个Job是负责启动SparkStreaming中的Receive的。可以这样理解:有一个独立的Stage产生了一个Job,通过这个 Job来启动我们的Receive进行数据的接收。从RDD角度来看,这个Job是由action触发的。

有具体运行来看,它是由一个Task完成的,也就是数据的接收是由一个Executor中的一个Task来负责的,其实这个过程与其他Job运行时相似的,只不过是这个Job不单单仅有一个Stage,而且Stage内部也仅有一个Task,但处理逻辑与其他Job是相似的。从这一点我们可以看出,一个Application中可以启动多个Job,并且可以让这些Job相互配合来完成一个业务或者程序,这一点可以用来编写复杂的程序。

而且此处是PROCESS_LOCAL,内存本地性,这说明了Spark默认情况时MEMORY_AND_DISK_ONLY的,由于我们发送的信息比较小,可以在内存中存下,所以就使用了内存本地性了。

对于Job 2:

这个DAG图与我们在onlineBlackListFilter类代码中的业务逻辑是一致的,但是我们写的是以DStream为基础的,这里的DAG中显示的却是以RDD为基础的,这一点疑问先留下,后面我们会进一步讲解。

我们对这个Job里面的一个Stage观察下,可以发现:


Task是运行在两个节点上的(这里集群有两个worker)。这说明,即使Spark Streaming只有一个executor(worker)负责接收数据,但是处理数据的确实我们集群中所有的worker中的Executor,这就最大化的利用了集群的资源。其实,这里数据是由副本的,所以自动就分布在了两台机器上,如果集群的节点数大于2的话,会通过网络来进行数据的分布,这点后面我们会讲到。在该Stage结束时,会有shuffle write的过程,将数据写到相当于Spark core中Driver 里的MapOutputTracker里,供下一个Stage读取。

对于Stage4,我们可以看见它与Stage2类似,也是在我们集群中所有机器上来运行的,也会有shuffle write的过程。


 

而在stage5,我们发现此时他又由一个Stage中的一个Task来执行了。但是会有shuffle过程,可以看见在AggregatedMetrics by Executor栏目中的Shuffle Read size/records处观察展示的shuffle信息。

可以发现该Stage是在 00:45:00产生的transform(transform@00:45:00)

在这个Stgae会经过shuffleRead 读取数据并在Worker2上的一个Executor中,由一个Task进行leftOuterJoin、filter、map操作,产生最后我们结果。

而Job 3和Job 4 与Job 2基本上是类似的。

Job 3:

Job 4:

我们发现Job 3和Job 4强前两个Stageskip掉了,是因为此时经过第一个Job产生的数据已在内存中,而Job 3 和 Job 4可以复用这些结果,所以Job 3 与Job 4直接跳过了。但是这里有一个奇怪的地方,为什么Job 3与Job 4的最后一个Stage与前面的Job 3逻辑是相同的,但是却要重新进行计算呢?打开Job 3的最后一个Stage,我们发现,他并不是由一个Worker节点的Executor中的一个Task来运行的,而是分散在集群中运行的,所以不能也Skip掉了。

对于Job 4也有类似的现象,

就从我们业务逻辑来讲,实质上只有一个Batch的数据进行了一次计算,而SparkStreaming为什么会为我们产生这么多的Job呢?对于Spark Streaming来说肯定不是无关紧要的。它们到底在Spark Streaming运行时起着什么样的角色?先买个关子,以后我们会一一详细讲解。

通过Spark Streaming运行的现象,我们发现SparkStreaming中有太多的我们不知道的东西了,并不像官网上所讲的那么简单,这正是值得我们深入研究其奥妙的价值所在。

最后,我们大体上讲下SparkStreaming的原理。

Spark Streaming本身是根据从不同的输入源(例如Kafka、Flume、HDFS等)流入的数据,按照时间为单位(一个Batch interval)将数据划分为一个集合,产生一个Job,然后触发这个Job在集群中运行,来对数据集合进行处理的流式数据处理的引擎。

从根本上讲,它是加上了时间维度的批处理,例如例子中的300s是一个Batch interval,所以每个300s,它就会产生一个RDD,进而基于这个RDD就会触发Job,而RDD的生成与Job的生成、处理都是由SparkStreaming框架管理的。

在Spark Streaming中有一个至关重要的概念-----DStream,因为每个一段时间都会产生RDD,以及这些RDD的依赖,会触发Job、然后会具体执行Job。由于时间是持续不断的,而RDD又是基于时间,每个固定的间隔产生的,为了更好的处理和管理这些RDD,引入了DStream,可以简单理解DStream就是一个无边际、无限大的集合,每个一定的时间间隔会往这个集合中加入新的RDD。可以认为DStream是一个时间+空间的观念,在时间维度上,每隔一定时间产生新的RDD加入DStream,在空间上我们对新加入的RDD(或整个DStream里的RDD集合)进行的处理。所以,从这一点来看,Spark Streaming会给研究者带来无限的乐趣。

 

在空间上,对DStream的一系列操作(及我们编程时所写的业务逻辑代码)会产生DStream Graph,如下图


其中T1、T2分别代表两个不同的数据来源,对于不同的数据来源的数据我们会进行join操作,产生Join DStream,然后进行map操作产生Mapped DStream,最后我们进行了3个foreach操作,每个foreach都会产生Job。在进行第一个foreach之前,进行了map操作;第二个直接进行foreach操作;第三个则先进行了reduce操作,最终多产生了Foreach DStream。这样就每个foreach DStream都产生了一个DAG的依赖。

这个DStreamGraph相当于一个模板,每隔一个时间间隔(Batch interval)就会产生RDD,而这些RDD的依赖关系就来自于这个模板,或者说RDD 的DAG Graph就是将我们这个模板(DStream Graph)实例化后的结果。如下图

从另一个角度来讲每当到达一个Batch interval时,在这个节点上,时间定格下来,确定时间后就产生了模板的实例,即空间上RDD的处理逻辑产生的RDD Graph。然后触发具体的Job来进行Job执行。

说了这么多,我们来看看官网上的介绍吧:

从官网上来看,SparkStreaming是在Spark core基础上的一个延伸的处理在线数据流的Spark API框架,这个框架是可扩展、高吞吐高容错的。(可扩展和高容错都是由Spark继承的,而由于Spark Streaming每个worker节点都可以做为Receive来接收数据,可想而知它的吞吐量必然会相当大,同时Spark的处理数据能力也说明了其吞吐能力)数据可以有多种来源,也可以同时接收多种数据来源的数据,例如Kafka、Flume、TCP Socket等。对于数据的处理可以利用Spark高阶函数例如map、reduce、join和window函数,处理后的数据可以放到本地文件系统、数据库和dashboards中。并且在处理数据时,我们可以利用在data streams上实时利用机器学习和图计算的算法。

在内部,Spark Streaming利用其receive接收实时的输入数据,并按照时间间隔Batch interval将数据划分成不同的批次。进而交给Spark引擎在该批次中产生final Stream进而产生final RDD,有action触发Job,运行Job获得结果。

DStream其实就是一个离散流的概念,代表了一个连续系统中的数据。它可以由Kafka、flume、TCP Socket接收的输入流数据创建,也可以由高阶函数(map、reduce等)作用于其他DStream来创建。在内部,DStream就是一系列RDD的序列(由Batch interval确定的)。

         至此,我们对SparkStreaming有了一定的理解,也存在一些问题,日后我们会一一解决的。





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值