一、StreamingContext源码分析

###入口 org.apache.spark.streaming/StreamingContext.scala

/**
* 在创建和完成StreamContext的初始化之后,创建了DStreamGraph、JobScheduler等关联组件之后,就会调用StreamContext的socketTextStream等方法,
* 来创建输入DStream,然后针对输入DStream执行一系列的transformation转换操作,最后,会执行一个output输出操作,来触发针对一个一个的batch的job触发和执行
*
* 上述初始化操作完成之后,start()方法是必须要调用的,不调用的话,相当于整个Spark Streaming应用程序不会执行
* StreamingContext.start(),启动一个Spark Streaming应用程序,这个start()方法,会创建StreamingContext的另外两个重要组件
* ReceiverTracker、JobGenerator,另外,最重要的的,启动整个Spark Streaming应用程序输入的DStream对应的Receiver,在Spark
* 集群的某个worker节点上的Executor中启动Receiver
*/
class StreamingContext private[streaming] (
sc_ : SparkContext,
cp_ : Checkpoint,
batchDur_ : Duration
) extends Logging { ##DStreamGraph // 重要组件,DStreamGraph,里面保存了,我们定义的Spark Streaming Application中,一系列的DStream的依赖关系以及互相之间的算子的应用
private[streaming] val graph: DStreamGraph = {
if (isCheckpointPresent) {
cp_.graph.setContext(this)
cp_.graph.restoreCheckpointData()
cp_.graph
} else {
assert(batchDur_ != null, "Batch duration for streaming context cannot be null")
val newGraph = new DStreamGraph()
newGraph.setBatchDuration(batchDur_)
newGraph
}
} ##JobScheduler // JobScheduler,涉及到job的调度,JobGenerator会负责每隔batch interval,生成一个job,然后通过JobScheduler来调度和提交job
// 底层,其实还是基于Spark的核心计算引擎,底层DAGScheduler、TaskScheduler、Worker、Executor、Task,如果定义了reduceByKey,
// 还是会走shuffle,底层的数据存取组件,还是Executor关联的BlockManager,负责持久化数据存储的组件,还是CacheManager
private[streaming] val scheduler = new JobScheduler(this) ##StreamingContext 的start()方法 /**
* 这个,就是Streaming应用程序启动的入口
*/
def start(): Unit = synchronized {
if (state == Started) {
throw new SparkException("StreamingContext has already been started")
}
if (state == Stopped) {
throw new SparkException("StreamingContext has already been stopped")
}
validate()
sparkContext.setCallSite(DStream.getCreationSite())
// 调用JobScheduler的start()方法
scheduler.start()
state = Started
}

调用JobScheduler的start()方法,看看这个方法

###org.apache.spark.streaming.scheduler/JobScheduler.scala
/**
* StreamingContext的start()方法,其实是比较简单的,真正重要的是,调用JobScheduler的start()方法
*/
def start(): Unit = synchronized {
if (eventActor != null) return // scheduler has already been started logDebug("Starting JobScheduler")
eventActor = ssc.env.actorSystem.actorOf(Props(new Actor {
def receive = {
case event: JobSchedulerEvent => processEvent(event)
}
}), "JobScheduler") listenerBus.start()
// 创建了ReceiverTracker组件,数据接收相关
receiverTracker = new ReceiverTracker(ssc)
// 并启动
// 至此,我们说的StreamingContext相关几个重要组件,都创建出来了,
// 然后,启动DStream关联的Receiver,逻辑都在ReceiverTracker的start()方法中
receiverTracker.start()
// 这是JobGenerator,创建JobScheduler的时候,直接就把JobGenerator给创建出来了,启动
jobGenerator.start()
logInfo("Started JobScheduler")
}

看ReceiverTracker的start()方法

###org.apache.spark.streaming.scheduler/ReceiverTracker.scala

  def start() = synchronized {
if (actor != null) {
throw new SparkException("ReceiverTracker already started")
} if (!receiverInputStreams.isEmpty) {
actor = ssc.env.actorSystem.actorOf(Props(new ReceiverTrackerActor),
"ReceiverTracker")
// 这个start()方法中,主要就是调用了内部的ReceiverLauncher的start()方法,这个ReceiverTracker
// 的主要作用,就是启动Receiver
if (!skipReceiverLaunch) receiverExecutor.start()
logInfo("ReceiverTracker started")
}
}

调用了receiverExecutor.start()方法,receiverExecutor是ReceiverTracker内部的ReceiverLauncher类

###org.apache.spark.streaming.scheduler/ReceiverTracker.scala

  class ReceiverLauncher {
@transient val env = ssc.env
@volatile @transient private var running = false
@transient val thread = new Thread() {
override def run() {
try {
SparkEnv.set(env)
// 开始启动所有的DStream对应的Receiver
startReceivers()
} catch {
case ie: InterruptedException => logInfo("ReceiverLauncher interrupted")
}
}
} // ReceiverLauncher的start()方法,其实启动了内部的一个线程,相当于使用异步的方式来启动Receiver
def start() {
thread.start()
}

receiverExecutor.start(),通过 startReceivers(),开始启动所有的DStream对应的Receiver

看这个方法

###org.apache.spark.streaming.scheduler/ReceiverTracker.scala

 /**
* 一直到这里,ReceiverTracker的startReceivers()都是在Driver上执行的
*/
private def startReceivers() { // 将程序中创建的所有的DStream,调用其getReceiver()方法,拿到一个Receiver集合
val receivers = receiverInputStreams.map(nis => {
//
val rcvr = nis.getReceiver()
rcvr.setReceiverId(nis.id)
rcvr
}) // 拿到这些Receiver的一些最佳位置
// Right now, we only honor preferences if all receivers have them
val hasLocationPreferences = receivers.map(_.preferredLocation.isDefined).reduce(_ && _) // Create the parallel collection of receivers to distributed them on the worker nodes
val tempRDD =
if (hasLocationPreferences) {
val receiversWithPreferences = receivers.map(r => (r, Seq(r.preferredLocation.get)))
ssc.sc.makeRDD[Receiver[_]](receiversWithPreferences)
} else {
ssc.sc.makeRDD(receivers, receivers.size)
} val checkpointDirOption = Option(ssc.checkpointDir)
val serializableHadoopConf = new SerializableWritable(ssc.sparkContext.hadoopConfiguration) // Function to start the receiver on the worker node
// 这里,定义了启动Receiver的核心逻辑
// 只是定义而已,不是在这里执行的,定义了一个startReceiver函数
// 这个函数的执行,以及后面的过程,都是在executor上执行的,Receiver的启动,是在executor上的,而不是driver
val startReceiver = (iterator: Iterator[Receiver[_]]) => {
if (!iterator.hasNext) {
throw new SparkException(
"Could not start receiver as object not found.")
}
val receiver = iterator.next()
// 将每一个Receiver封装在ReceiverSupervisorImpl中,并调用其start()方法,启动
val supervisor = new ReceiverSupervisorImpl(
receiver, SparkEnv.get, serializableHadoopConf.value, checkpointDirOption)
supervisor.start()
supervisor.awaitTermination()
}
// Run the dummy Spark job to ensure that all slaves have registered.
// This avoids all the receivers to be scheduled on the same node.
if (!ssc.sparkContext.isLocal) {
ssc.sparkContext.makeRDD(1 to 50, 50).map(x => (x, 1)).reduceByKey(_ + _, 20).collect()
} // Distribute the receivers and start them
logInfo("Starting " + receivers.length + " receivers")
running = true
// 调用StreamingContext的SparkContext的runJob()方法,真正的,将启动Receiver的startReceiver函数
// 分布到各个worker节点的executor上去执行
ssc.sparkContext.runJob(tempRDD, ssc.sparkContext.clean(startReceiver))
running = false
logInfo("All of the receivers have been terminated")
}

二、Receiver源码

看下receiverInputStreams是什么

###org.apache.spark.streaming.scheduler/ReceiverTracker.scala

// 这个receiverInputStreams,就是从StreamingContext的graph中,取出的,就是说,每次调用StreamingContext创建一个输入DStream时,都会
// 放入DStreamGraph的ReceiverInputStreams
private val receiverInputStreams = ssc.graph.getReceiverInputStreams()

输入DStram,一定都会有一个重要的方法,getReceiver(),如SocketInputDStream

###org.apache.spark.streaming.dstream/SocketInputDStream.scala

private[streaming]
class SocketInputDStream[T: ClassTag](
@transient ssc_ : StreamingContext,
host: String,
port: Int,
bytesToObjects: InputStream => Iterator[T],
storageLevel: StorageLevel
) extends ReceiverInputDStream[T](ssc_) { // 输入DStram,一定都会有一个重要的方法,getReceiver(),这个方法就负责返回DStream的Receiver
def getReceiver(): Receiver[T] = {
new SocketReceiver(host, port, bytesToObjects, storageLevel)
}
}

接下来supervisor.start()方法,supervisor是ReceiverSupervisorImpl类的对象,ReceiverSupervisorImpl类并没有start()方法,

该start()在ReceiverSupervisorImpl的父类ReceiverSupervisor里面

看看ReceiverSupervisor的start()方法

###org.apache.spark.streaming.receiver/ReceiverSupervisor.scala

  /** Called when supervisor is started */
protected def onStart() { }
/** Called when receiver is stopped */
protected def onReceiverStop(message: String, error: Option[Throwable]) { } /** Start the supervisor */
def start() {
onStart()
startReceiver()
}

ReceiverSupervisor的start()调用了onStart()方法,而ReceiverSupervisor是抽象方法,

所以应该看实现类的的onStart(),看ReceiverSupervisorImpl的onStart()方法

###org.apache.spark.streaming.receiver/ReceiverSupervisorImpl.scala

  override protected def onStart() {
// 这里的blockGenerator很重要,和数据接收有关,其运行在worker的executor端负责数据接收后的一些存取工作,以及配合ReceiverTracker
// 在Executor上,启动Receiver之前,就会先启动这个Receiver相关的一个blockGenerator,该组件,在数据接收中,极其重要
blockGenerator.start()
}

再次回到JobScheduler的start()方法,调用了jobGenerator.start()方法,

###org.apache.spark.streaming.scheduler/JobGenerator.scala

  def start(): Unit = synchronized {
if (eventActor != null) return // generator has already been started eventActor = ssc.env.actorSystem.actorOf(Props(new Actor {
def receive = {
case event: JobGeneratorEvent => processEvent(event)
}
}), "JobGenerator")
if (ssc.isCheckpointPresent) {
restart()
} else {
startFirstTime()
}
}

看startFirstTime()方法

###org.apache.spark.streaming.scheduler/JobGenerator.scala

/**
* 只要JobGenerator一启动,这里就初始化一个开始时间,后面,根据我们自己的batch interval,每到一个batch interval
* 都会从上一个time,也就是这里的startTime,开始将batch interval内的数据封装成一个batch
*/
private def startFirstTime() {
val startTime = new Time(timer.getStartTime())
graph.start(startTime - graph.batchDuration)
timer.start(startTime.milliseconds)
logInfo("Started JobGenerator at " + startTime)
}

看DStream的output方法,比如print

###org.apache.spark.streaming.dstream/DStream.scala

def print() {
print(10)
} /**
* Print the first num elements of each RDD generated in this DStream. This is an output
* operator, so this DStream will be registered as an output stream and there materialized.
*/
def print(num: Int) {
def foreachFunc = (rdd: RDD[T], time: Time) => {
val firstNum = rdd.take(num + 1)
println ("-------------------------------------------")
println ("Time: " + time)
println ("-------------------------------------------")
firstNum.take(num).foreach(println)
if (firstNum.size > num) println("...")
println()
}
new ForEachDStream(this, context.sparkContext.clean(foreachFunc)).register()
}

ForEachDStream类

###org.apache.spark.streaming.dstream/ForEachDStream.scala

class ForEachDStream[T: ClassTag] (
parent: DStream[T],
foreachFunc: (RDD[T], Time) => Unit
) extends DStream[Unit](parent.ssc) { override def dependencies = List(parent) override def slideDuration: Duration = parent.slideDuration override def compute(validTime: Time): Option[RDD[Unit]] = None // 所有的output操作,其实都会来调用ForEachDStream的generateJob()方法,所以,每次执行DStreamGraph的
// 时候,到最后,都会调用到这里,底层会触发job的提交
override def generateJob(time: Time): Option[Job] = {
parent.getOrCompute(time) match {
case Some(rdd) =>
val jobFunc = () => {
ssc.sparkContext.setCallSite(creationSite)
foreachFunc(rdd, time)
}
Some(new Job(time, jobFunc))
case None => None
}
}
}

64、Spark Streaming:StreamingContext初始化与Receiver启动原理剖析与源码分析的更多相关文章

  1. 65、Spark Streaming:数据接收原理剖析与源码分析

    一.数据接收原理 二.源码分析 入口包org.apache.spark.streaming.receiver下ReceiverSupervisorImpl类的onStart()方法 ### overr ...

  2. 66、Spark Streaming:数据处理原理剖析与源码分析(block与batch关系透彻解析)

    一.数据处理原理剖析 每隔我们设置的batch interval 的time,就去找ReceiverTracker,将其中的,从上次划分batch的时间,到目前为止的这个batch interval ...

  3. Spark Streaming从Flume Poll数据案例实战和内幕源码解密

    本节课分成二部分讲解: 一.Spark Streaming on Polling from Flume实战 二.Spark Streaming on Polling from Flume源码 第一部分 ...

  4. Spark GraphX图计算简单案例【代码实现,源码分析】

    一.简介 参考:https://www.cnblogs.com/yszd/p/10186556.html 二.代码实现 package big.data.analyse.graphx import o ...

  5. springboot之启动原理解析及源码阅读

    前言 SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面 ...

  6. Phalcon的Mvc结构及启动流程(部分源码分析)

    Phalcon本身有支持创建多种形式的Web应用项目以应对不同场景,包括迷你应用.单模块标准应用.以及较复杂的多模块应用 创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalc ...

  7. Phalcon Framework的Mvc结构及启动流程(部分源码分析)

    创建项目 Phalcon环境配置安装后,可以通过命令行生成一个标准的Phalcon多模块应用 phalcon project eva --type modules入口文件为public/index.p ...

  8. spark源码分析以及优化

    第一章.spark源码分析之RDD四种依赖关系 一.RDD四种依赖关系 RDD四种依赖关系,分别是 ShuffleDependency.PrunDependency.RangeDependency和O ...

  9. Spring源码分析:非懒加载的单例Bean初始化前后的一些操作

    之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...

随机推荐

  1. 通过分析 WPF 的渲染脏区优化渲染性能

    原文:通过分析 WPF 的渲染脏区优化渲染性能 本文介绍通过发现渲染脏区来提高渲染性能. 本文内容 脏区 Dirty Region WPF 性能套件 脏区监视 优化脏区重绘 脏区 Dirty Regi ...

  2. AutoFac的简单使用教程

    Autofac可以对代码进行依赖注入,实现控制反转.以下是本菜鸟在初次入门时的代码配置,其源码,内部原理都还有待日后研究.目前也只是仅仅做到了能够使项目正常使用而已. 跟我一样刚刚入门的菜鸟朋友们可以 ...

  3. selenium中的元素操作之三大等待(一)

    等待时做什么,为什么使用等待 在做自动化测试,设计测试用例的时候,有时下一步的操作会依赖上一步的结果或者内容,上一步操作成功之后才能进行下一步操作等,这时候,我们就需要使用等待,来判断上一步操作是否完 ...

  4. 基于MUI框架+HTML5PLUS 开发 iOS和Android 应用程序(APP)

    目录 事前准备 创建项目 利用MUI写一个简单的页面 关于文件打包 事前准备 # 软件 HBuilder X Web开发IDE 下载地址:https://www.dcloud.io/hbuilderx ...

  5. iOS完整学习路线

    来源:http://www.cnblogs.com/mjios/p/3226954.html

  6. MongoDB 设置参数

    服务器配置文件分析 bin目录下的mongod.cfg是服务器的配置文件,文件中主要的配置参数: 1.数据库文件的存放位置 2.服务器日志文件的存放位置 3.默认的IP地址.端口号 设置密码 默认情况 ...

  7. 理解 Node.js 中 Stream(流)

    Stream(流) 是 Node.js 中处理流式数据的抽象接口. stream 模块用于构建实现了流接口的对象. Node.js 提供了多种流对象. 例如,对 HTTP 服务器的request请求和 ...

  8. glfw之hello world

    mac上用cocoa做imshow,资料似乎不好找,即便找到也需要和OC混编,而不是纯C.这不够纯粹.考虑用opengl做通用的.跨平台的imshow.先入门一下opengl,从glfw官方例子入手. ...

  9. mongodb驱动接口

    mongodb对外接口或驱动:https://docs.mongodb.com/ecosystem/drivers/,包含C,C++,Go,Python等. C驱动 mongodb的C驱动,即libm ...

  10. 前端JS

    目录 1.javascript介绍 1.1Web前端有三层: 1.2其中JavaScript基础又分为三个部分: 1.3JavaScript入门易学性 1.4JavaScript的组成 1.5Java ...