Spark 定制版:005~贯通Spark Streaming流计算框架的运行源码

来源:互联网 发布:卸载软件设置密码 编辑:程序博客网 时间:2024/06/14 11:08

本讲内容:

a. 在线动态计算分类最热门商品案例回顾与演示
b. 基于案例贯通Spark Streaming的运行源码

注:本讲内容基于Spark 1.6.1版本(在2016年5月来说是Spark最新版本)讲解。

上节回顾

上节课主要从事务视角为大家探索Spark Streaming架构机制;Spark Streaming程序分成而部分,一部分是Driver,另外一部分是Executor。通过对Driver和Executor解析,洞察怎么才能完成完整的语义、事务一致性,并保证数据的零丢失,Exactly Once的事务处理。

而直接通过kafka direct api直接消费数据,所有的Executors通过kafka api直接消费数据,直接管理offset,所以不会重复消费数据;从而实现事务!!!

设置spark.task.maxFailures次数为1、spark.speculation为关闭状态、auto.offset.reset为“largest”的方式来解决Spark Streaming数据输出多次重写的问题

最后可以通过transform和foreachRDD基于业务逻辑代码进行逻辑控制来实现数据不重复消费和输出不重复!这二个方法类似于spark的后门,可以做任意想象的控制操作!

开讲

本讲,主要是从事例代码开始,并通过Spark源代码给大家贯通Spark Streaming流计算框架的运行。

下面让我们开始神奇的探秘之旅吧?!

首先,本讲我们从Spark Streaming+Spark SQL来实现分类最热门商品的在线动态计算的事例代码演示开始,如下图:

事例源码

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

开启源码神奇之旅

从实例化SteamingContext入手,进入StreamingContext源码

这里写图片描述

在实例化spark Streaming的时候,从一下源码中我们可以清晰明白SparkStreaming 是SparkCore的一个应用程序

这里写图片描述

这里写图片描述

进入到createNewSparkContext的源码中

SteamingContext.scala,886行代码
这里写图片描述

由以上源码我们可以确SparkStreaming 就是是SparkCore的一个应用程序

下面我们看看分案例的数据流是怎么创建的?

这里写图片描述

由以上代码看到是socketTextStream方法开启了一个数据流入端口,进入到socketTextStream源码中

这里写图片描述

可看到代码最后面调用socketStream,实际上生成SocketInputDStream,进入到SocketInputDStream源码

这里写图片描述

SocketInputDStream继承ReceiverInputDStream,进入到ReceiverInputDStream源码中

这里写图片描述
  
ReceiverInputDStream继承InputDStream,进入到InputDStream中

这里写图片描述

由以上代码可以总结出SocketInputDStream的继承关系如下:

SocketInputDStream -> ReceiverInputDStream -> InputDStream -> DStream

最终我们又回到了DStream中,进入到DStream源码中

这里写图片描述

看到以上源码信息,我们不得不重新思考一个问题了,就是DStream和RDD是什么关系呢?

由之前的几讲我们知道Spark Streaming是基于DStream编程。DStream是逻辑级别的,而RDD是物理级别的。DStream是随着时间的流动内部将集合封装RDD。对DStream的操作,归根结底还是对其RDD进行的操作。

下面我们从源码中,进一步分析一下上面的话

这里写图片描述

再看看DStream的getOrCompute

这里写图片描述

这样我们可以总结到:DStream是生成RDD的模板,再将生成的RDD放在HashMap中(具体生成RDD过程以后剖析),当达到Interval的时候这些模板会被BatchData实例化成为RDD和DAG。

下面我们紧接着绘制了Spark Streaming应用运行流程的关键类,如下图所示

来自(上海-丁立清)
这里写图片描述

我们将根据上图绘制步骤逐步为大家开始神秘之旅:

进入到ScreamingContext源码,找到ScreamingContext的start(),详情如下

启动JobScheduler

这里写图片描述

由上面源码可以知道,找到ScreamingContext的start(),会启动JobScheduler。

进入到JobScheduler源码,来看下JobScheduler的启动过程start()

这里写图片描述

由上面源码可以知道,JobScheduler的启动过程start()启动了EventLoop(启动消息循环处理线程,用于处理JobScheduler的各种事件)、StreamListenerBus(启动监听器,用于更新Spark UI中StreamTab的内)、 InputInfoTracker(用于管理所有的输入的流,以及他们输入的数据统计,这些信息将通过 StreamingListener监听)、ReceiverTracker( 启动ReceiverTracker,用于处理数据接收、数据缓存、Block生成)和jobGenerator(启动JobGenerator,用于DStreamGraph初始化、DStream与RDD的转换、生成Job、提交执行等工作)等多项工作

这里写图片描述

JobScheduler中的消息处理函数processEvent,处理三类消息:Job已开始,Job已完成,错误报告

走到这里,我们已经简单的知道了JobScheduler.start()中启动的工作,此时我们可以回过头来细细品味JobScheduler,那么我们就从EventLoop开始

进入到EventLoop源码,洞悉EventLoop中的事件

这里写图片描述

紧接着是StreamListenerBus;用于异步传递StreamingListenerEvents注册到StreamingListeners。用于更新Spark UI中StreamTab的内容。

这里写图片描述

那么我们再接着看InputInfoTracker

这里写图片描述
这里写图片描述

ReceiverTracker的start()中,会内部实例化ReceiverTrackerEndpoint这个Rpc消息通信体

这里写图片描述

由上面的源码可知在ReceiverTracker启动的过程中会调用其launchReceivers方法

这里写图片描述
这里写图片描述

其中调用了runDummySparkJob方法来启动Spark Streaming的框架第一个Job

这里写图片描述

其中collect这个action操作会触发Spark Job的执行。这个方法是为了确保每个Slave都注册上,避免所有Receiver都在一个节点,使后面的计算能负载均衡。

ReceiverTracker.launchReceivers()还调用了endpoint.send(StartAllReceivers(receivers))方法,Rpc消息通信体发送StartAllReceivers消息。

ReceiverTrackerEndpoint它自己接收到消息后,先根据调度策略获得Recevier在哪个Executor上运行,然后在调用startReceiver(receiver, executors)方法,来启动Receiver。

这里写图片描述

在startReceiver方法中,ssc.sparkContext.submitJob提交Job的时候传入startReceiverFunc这个方法,因为startReceiverFunc该方法是在Executor上执行的。而在startReceiverFunc方法中是实例化

这里写图片描述
这里写图片描述

ReceiverSupervisorImpl对象,该对象是对Receiver进行管理和监控。这个Job是Spark Streaming框架为我们启动的第二个Job,且一直运行。因为supervisor.awaitTermination()该方法会阻塞等待退出。

这里写图片描述
这里写图片描述

我们再仔细看下ReceiverSupervisorImpl的启动过程,先启动所有注册上的BlockGenerator对象,然后向ReceiverTrackerEndpoint发送RegisterReceiver消息,再调用receiver的onStart方法。

这里写图片描述
这里写图片描述

其中在Driver运行的ReceiverTrackerEndpoint对象接收到RegisterReceiver消息后,将streamId, typ, host, executorId, receiverEndpoint封装为ReceiverTrackingInfo保存到内存对象receiverTrackingInfos这个HashMap中。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

最后我们来看JobScheduler.start()中启动的JobGenerator
  JobGenerator有成员RecurringTimer(根据创建StreamContext时传入的batchInterval,定时发送GenerateJobs消息),用于消息循环处理线程(eventLoop.start())和定时生成Job的定时器(startFirstTime())。按照batchInterval时间间隔定期发送GenerateJobs消息。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

DStreamGraph的generateJobs方法,调用输出流(outputStreams :具体Action的输出操作)的generateJob方法来生成Jobs集合

这里写图片描述
这里写图片描述

来看下DStream的generateJob方法,调用getOrCompute方法来获取当Interval的时候,DStreamGraph会被BatchData实例化成为RDD,如果有RDD则封装jobFunc方法,里面包含context.sparkContext.runJob(rdd, emptyFunc),然后返回封装后的Job。

这里写图片描述

接下来看JobScheduler的submitJobSet方法,向线程池中提交JobHandler。而JobHandler实现了Runnable 接口,最终调用了job.run()这个方法。看一下Job类的定义,其中run方法调用的func为构造Job时传入的jobFunc,其包含了context.sparkContext.runJob(rdd, emptyFunc)操作,最终导致Job的提交。

这里写图片描述

这里写图片描述

这里写图片描述

至此,Job 运行。

补充:

前面我们讲过,块数据的 meta 信息上报到 ReceiverTracker,然后交给 ReceivedBlockTracker 做具体的管理。ReceivedBlockTracker 也采用 WAL 冷备方式进行备份,在 driver 失效后,由新的 ReceivedBlockTracker 读取 WAL 并恢复 block 的 meta 信息。另外,需要定时对 DStreamGraph 和 JobScheduler 做 Checkpoint,来记录整个 DStreamGraph 的变化、和每个 batch 的 job 的完成情况。

注意到这里采用的是完整 checkpoint 的方式,和之前的 WAL 的方式都不一样。Checkpoint 通常也是落地到可靠存储如 HDFS。Checkpoint 发起的间隔默认的是和 batchDuration 一致;即每次 batch 发起、提交了需要运行的 job 后就做 Checkpoint,另外在 job 完成了更新任务状态的时候再次做一下 Checkpoint。

这样一来,在 driver 失效并恢复后,可以读取最近一次的 Checkpoint 来恢复作业的 DStreamGraph 和 job 的运行及完成状态。

2 0
原创粉丝点击