`

5.Spark Streaming流计算框架的运行流程源码分析

阅读更多
1 spark streaming 程序代码实例
代码如下:
  1. object OnlineTheTop3ItemForEachCategory2DB {  
  2.   def main(args: Array[String]){   
  3.     val conf = new SparkConf() //创建SparkConf对象  
  4.     //设置应用程序的名称,在程序运行的监控界面可以看到名称  
  5.     conf.setAppName("OnlineTheTop3ItemForEachCategory2DB")  
  6.     conf.setMaster("spark://Master:7077") //此时,程序在Spark集群  
  7.     //设置batchDuration时间间隔来控制Job生成的频率并且创建Spark Streaming执行的入口  
  8.     val ssc = new StreamingContext(conf, Seconds(5))  
  9.     ssc.checkpoint("/root/Documents/SparkApps/checkpoint")  
  10.     val soketDStream = ssc.socketTextStream("Master", 9999)  
  11.     /// 业务处理逻辑 .....
  12.       
  13.     ssc.start()  
  14.     ssc.awaitTermination()  
  15.   }  
  16. }  
 
2 Spark Streaming的运行源码分析

2.1 创建StreamingContext

 
我们将基于以上实例例,粗略地分析一下Spark源码,提示一些有针对性的内容,以了解其运行的主要流程。
1)代码没有直接使用SparkContext,而是使用StreamingContext。
我们来看看StreamingContext 的源码片段:
  1. /**  
  2.  * Create a StreamingContext by providing the configuration necessary for a new SparkContext.  
  3.  * @param conf a org.apache.spark.SparkConf object specifying Spark parameters  
  4.  * @param batchDuration the time interval at which streaming data will be divided into batches  
  5.  */  
  6. def this(conf: SparkConf, batchDuration: Duration) = {  
  7.   this(StreamingContext.createNewSparkContext(conf), null, batchDuration)  
  8. }  
没错,createNewSparkContext就是创建SparkContext:
  1. private[streaming] def createNewSparkContext(conf: SparkConf): SparkContext = {   
  2.   new SparkContext(conf)   
  3. }  
 这说明Spark Streaming也是Spark上的一个应用程序。

 2)案例最开始的地方肯定要通过数据流创建一个InputDStream。

  1. val socketDstram = ssc.socketTextStream("Master", 9999)  
socketTextStream方法定义如下:
  1. /**  
  2.  * Create a input stream from TCP source hostname:port. Data is received using  
  3.  * a TCP socket and the receive bytes is interpreted as UTF8 encoded `\n` delimited  
  4.  * lines.  
  5.  * @param hostname      Hostname to connect to for receiving data  
  6.  * @param port          Port to connect to for receiving data  
  7.  * @param storageLevel  Storage level to use for storing the received objects  
  8.  *                      (default: StorageLevel.MEMORY_AND_DISK_SER_2)  
  9.  */  
  10. def socketTextStream(  
  11.     hostname: String,  
  12.     port: Int,  
  13.     storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2  
  14.   ): ReceiverInputDStream[String] = withNamedScope("socket text stream") {  
  15.   socketStream[String](hostname, port, SocketReceiver.bytesToLines, storageLevel)  
  16. }  
3)可看到代码最后面调用socketStream。
socketStream定义如下:
  1. /**  
  2.  * Create a input stream from TCP source hostname:port. Data is received using  
  3.  * a TCP socket and the receive bytes it interepreted as object using the given  
  4.  * converter.  
  5.  * @param hostname      Hostname to connect to for receiving data  
  6.  * @param port          Port to connect to for receiving data  
  7.  * @param converter     Function to convert the byte stream to objects  
  8.  * @param storageLevel  Storage level to use for storing the received objects  
  9.  * @tparam T            Type of the objects received (after converting bytes to objects)  
  10.  */  
  11. def socketStream[T: ClassTag](  
  12.     hostname: String,  
  13.     port: Int,  
  14.     converter: (InputStream) => Iterator[T],  
  15.     storageLevel: StorageLevel  
  16.   ): ReceiverInputDStream[T] = {  
  17.   new SocketInputDStream[T](this, hostname, port, converter, storageLevel)  
  18. }  
4)实际上生成SocketInputDStream。
SocketInputDStream类如下:
  1. private[streaming]  
  2. class SocketInputDStream[T: ClassTag](  
  3.     ssc_ : StreamingContext,  
  4.     host: String,  
  5.     port: Int,  
  6.     bytesToObjects: InputStream => Iterator[T],  
  7.     storageLevel: StorageLevel  
  8.   ) extends ReceiverInputDStream[T](ssc_) {  
  9.   def getReceiver(): Receiver[T] = {  
  10.     new SocketReceiver(host, port, bytesToObjects, storageLevel)  
  11.   }  
  12. }  
SocketInputDStream继承ReceiverInputDStream。
其中实现getReceiver方法,返回SocketReceiver对象。
总结一下SocketInputDStream的继承关系:
SocketInputDStream -> ReceiverInputDStream -> InputDStream -> DStream。  
 
5)DStream是生成RDD的模板,是逻辑级别,当达到Interval的时候这些模板会被batch data实例化成为RDD和DAG。
DStream的generatedRDDs:
  1. // RDDs generated, marked as private[streaming] so that testsuites can access it  
  2. @transient  
  3. private[streaming] var generatedRDDs = new HashMap[Time, RDD[T]] ()  
DStream的getOrCompute:
  1. /**  
  2.  * Get the RDD corresponding to the given time; either retrieve it from cache  
  3.  * or compute-and-cache it.  
  4.  */  
  5. private[streaming] final def getOrCompute(time: Time): Option[RDD[T]] = {  
  6.   // If RDD was already generated, then retrieve it from HashMap,  
  7.   // or else compute the RDD  
  8.   generatedRDDs.get(time).orElse {  
  9.     // Compute the RDD if time is valid (e.g. correct time in a sliding window)  
  10.     // of RDD generation, else generate nothing.  
  11.     if (isTimeValid(time)) {  
  12.       val rddOption = createRDDWithLocalProperties(time, displayInnerRDDOps = false) {  
  13.         // Disable checks for existing output directories in jobs launched by the streaming  
  14.         // scheduler, since we may need to write output to an existing directory during checkpoint  
  15.         // recovery; see SPARK-4835 for more details. We need to have this call here because  
  16.         // compute() might cause Spark jobs to be launched.  
  17.         PairRDDFunctions.disableOutputSpecValidation.withValue(true) {  
  18.           compute(time)  
  19.         }  
  20.       }  
  21.       rddOption.foreach { case newRDD =>  
  22.         // Register the generated RDD for caching and checkpointing  
  23.         if (storageLevel != StorageLevel.NONE) {  
  24.           newRDD.persist(storageLevel)  
  25.           logDebug(s"Persisting RDD ${newRDD.id} for time $time to $storageLevel")  
  26.         }  
  27.         if (checkpointDuration != null && (time - zeroTime).isMultipleOf(checkpointDuration)) {  
  28.           newRDD.checkpoint()  
  29.           logInfo(s"Marking RDD ${newRDD.id} for time $time for checkpointing")  
  30.         }  
  31.         generatedRDDs.put(time, newRDD)  
  32.       }  
  33.       rddOption  
  34.     } else {  
  35.       None  
  36.     }  
  37.   }  
  38. }  
主要是生成RDD,再将生成的RDD放在HashMap中。具体生成RDD过程以后剖析。
目前大致讲了DStream和RDD这些核心概念在Spark Streaming中的使用。
 

2.2 启动StreamingContext

代码实例中的ssc.start() 方法启动StreamingContext,主要的逻辑发生在这个start方法中:

 

      *  在StreamingContext调用start方法的内部其实是会启动JobScheduler的Start方法,进行消息循环,

      *  在JobScheduler的start内部会构造JobGenerator和ReceiverTacker,并且调用JobGenerator和

      *  ReceiverTacker的start方法:

      *

      *  1,JobGenerator启动后会不断的根据batchDuration生成一个个的Job

 

      *  其实这里的Job不是Spark Core中所指的Job,它只是基于DStreamGraph而生成的RDD的DAG

      *  而已,从Java角度讲,相当于Runnable接口实例,此时要想运行Job需要提交给JobScheduler,

      *  在JobScheduler中通过线程池的方式找到一个单独的线程来提交Job到集群运行(其实是在线程中

      *  基于RDD的Action触发真正的作业的运行)

      *

 

      *  2,ReceiverTracker启动后首先在Spark Cluster中启动Receiver(其实是在Executor中先启动

      *  ReceiverSupervisor),在Receiver收到数据后会通过ReceiverSupervisor存储到Executor并且把

      *  数据的Metadata信息发送给Driver中的ReceiverTracker,在ReceiverTracker内部会通过

      *  ReceivedBlockTracker来管理接受到的元数据信息.

 

 

体现Spark Streaming应用运行流程的关键类如下图所示。
 
 
 
下面开启神奇的 源码分析之旅,过程痛苦,痛苦之后是大彻大悟的畅快...........
 
 
1)先看看ScreamingContext的start()。
start()方法启动StreamContext,由于Spark应用程序不能有多个SparkContext对象实例,所以Spark Streaming框架在启动时对状态进行判断。代码如下:
  1. /**  
  2.  * Start the execution of the streams.  
  3.  *  
  4.  * @throws IllegalStateException if the StreamingContext is already stopped.  
  5.  */  
  6. def start(): Unit = synchronized {  
  7.   state match {  
  8.     case INITIALIZED =>  
  9.       startSite.set(DStream.getCreationSite())  
  10.       StreamingContext.ACTIVATION_LOCK.synchronized {  
  11.         StreamingContext.assertNoOtherContextIsActive()  
  12.         try {  
  13.           validate()  
  14.           // Start the streaming scheduler in a new thread, so that thread local properties  
  15.           // like call sites and job groups can be reset without affecting those of the  
  16.           // current thread.  
  17.           //线程本地存储,线程有自己的私有属性,设置这些线程的时候不会影响其他线程,
  18.         ThreadUtils.runInNewThread("streaming-start") {  
  19.             sparkContext.setCallSite(startSite.get)  
  20.             sparkContext.clearJobGroup()  
  21.             sparkContext.setLocalProperty(SparkContext.SPARK_JOB_INTERRUPT_ON_CANCEL, "false")  
  22.             //启动JobScheduler  
  23.             scheduler.start()  
  24.           }  
  25.           state = StreamingContextState.ACTIVE  
  26.         } catch {  
  27.           case NonFatal(e) =>  
  28.             logError("Error starting the context, marking it as stopped", e)  
  29.             scheduler.stop(false)  
  30.             state = StreamingContextState.STOPPED  
  31.             throw e  
  32.         }  
  33.         StreamingContext.setActiveContext(this)  
  34.       }  
  35.       shutdownHookRef = ShutdownHookManager.addShutdownHook(  
  36.         StreamingContext.SHUTDOWN_HOOK_PRIORITY)(stopOnShutdown)  
  37.       // Registering Streaming Metrics at the start of the StreamingContext  
  38.       assert(env.metricsSystem != null)  
  39.       env.metricsSystem.registerSource(streamingSource)  
  40.       uiTab.foreach(_.attach())  
  41.       logInfo("StreamingContext started")  
  42.     case ACTIVE =>  
  43.       logWarning("StreamingContext has already been started")  
  44.     case STOPPED =>  
  45.       throw new IllegalStateException("StreamingContext has already been stopped")  
  46.   }  
  47. }  
初始状态时,会启动JobScheduler。
 
2)接着来看下JobScheduler的启动过程start()。
其中启动了EventLoop、StreamListenerBus、ReceiverTracker和jobGenerator等多项工作。
  1. def start(): Unit = synchronized {  
  2.   if (eventLoop != null) return // scheduler has already been started  
  3.     logDebug("Starting JobScheduler")  
  4.     eventLoop = new EventLoop[JobSchedulerEvent]("JobScheduler") {  
  5.     override protected def onReceive(event: JobSchedulerEvent): Unit = processEvent(event)  
  6.     override protected def onError(e: Throwable): Unit = reportError("Error in job scheduler", e)  
  7.   }  
  8.   // 启动消息循环处理线程。用于处理JobScheduler的各种事件。  
  9.   eventLoop.start()  
  10.   // attach rate controllers of input streams to receive batch completion updates  
  11.   for {  
  12.     inputDStream <- ssc.graph.getInputStreams  
  13. // rateController可以控制输入速度
  14.     rateController <- inputDStream.rateController  
  15.   } ssc.addStreamingListener(rateController)  
  16.   // 启动监听器。用于更新Spark UI中StreamTab的内容。  
  17.   listenerBus.start(ssc.sparkContext)  
  18.   receiverTracker = new ReceiverTracker(ssc)  
  19.   // 生成InputInfoTracker。用于管理所有的输入的流,以及他们输入的数据统计。这些信息将通过 StreamingListener监听。  
  20.   inputInfoTracker = new InputInfoTracker(ssc)  
  21.   // 启动ReceiverTracker。用于处理数据接收、数据缓存、Block生成。  
  22.   receiverTracker.start()  
  23.   // 启动JobGenerator。用于DStreamGraph初始化、DStream与RDD的转换、生成Job、提交执行等工作。  
  24.   jobGenerator.start()  
  25.   logInfo("Started JobScheduler")  
  26. }  
3)JobScheduler中的消息处理函数processEvent
处理三类消息:Job已开始,Job已完成,错误报告。
  1. private def processEvent(event: JobSchedulerEvent) {  
  2.   try {  
  3.     event match {  
  4.       case JobStarted(job, startTime) => handleJobStart(job, startTime)  
  5.       case JobCompleted(job, completedTime) => handleJobCompletion(job, completedTime)  
  6.       case ErrorReported(m, e) => handleError(m, e)  
  7.     }  
  8.   } catch {  
  9.     case e: Throwable =>  
  10.       reportError("Error in job scheduler", e)  
  11.   }  
  12. }
4)我们再粗略地分析一下JobScheduler.start()中启动的工作。
4.1)先看JobScheduler.start()启动的第一项工作EventLoop。
EventLoop用于处理JobScheduler的各种事件。
EventLoop中有事件队列:
  1. private val eventQueue: BlockingQueue[E] = new LinkedBlockingDeque[E]()  
还有一个线程处理队列中的事件:
  1. private val eventThread = new Thread(name) {  
  2.   setDaemon(true)  
  3.   override def run(): Unit = {  
  4.     try {  
  5.       while (!stopped.get) {  
  6.         val event = eventQueue.take()  
  7.         try {  
  8.           onReceive(event)  
  9.         } catch {  
  10.           case NonFatal(e) => {  
  11.             try {  
  12.               onError(e)  
  13.             } catch {  
  14.               case NonFatal(e) => logError("Unexpected error in " + name, e)  
  15.             }  
  16.           }  
  17.         }  
  18.       }  
  19.     } catch {  
  20.       case ie: InterruptedException => // exit even if eventQueue is not empty  
  21.       case NonFatal(e) => logError("Unexpected error in " + name, e)  
  22.     }  
  23.   }  
  24. }  
这个线程中的onReceive、onError,在JobScheduler中的EventLoop实例化时已定义。
4.2)JobScheduler.start()启动的第二项工作StreamListenerBus。
- 用于异步传递StreamingListenerEvents到注册的StreamingListeners。
- 用于更新Spark UI中StreamTab的内容。
 
 
4.3)看JobScheduler.start()启动的第三项工作ReceiverTracker。
ReceiverTracker用于管理所有的输入的流,以及他们输入的数据统计。
这些信息将通过 StreamingListener监听。
ReceiverTracker的start()中,会内部实例化ReceiverTrackerEndpoint这个Rpc消息通信体。
 
复制代码
 1 def start(): Unit = synchronized {
 2   if (isTrackerStarted) {
 3     throw new SparkException("ReceiverTracker already started")
 4   }
 5  
 6   if (!receiverInputStreams.isEmpty) {
 7     endpoint = ssc.env.rpcEnv.setupEndpoint(
 8       "ReceiverTracker", new ReceiverTrackerEndpoint(ssc.env.rpcEnv))
 9     if (!skipReceiverLaunch) launchReceivers()
10     logInfo("ReceiverTracker started")
11     trackerState = Started
12   }
13 }
复制代码

 

 
在ReceiverTracker启动的过程中会调用其launchReceivers方法:
 
复制代码
/**
 * Get the receivers from the ReceiverInputDStreams, distributes them to the
 * worker nodes as a parallel collection, and runs them.
 */
private def launchReceivers(): Unit = {
  val receivers = receiverInputStreams.map(nis => {
    val rcvr = nis.getReceiver()
    rcvr.setReceiverId(nis.id)
    rcvr
  })
  runDummySparkJob()
  logInfo("Starting " + receivers.length + " receivers")
  endpoint.send(StartAllReceivers(receivers))
}
 
复制代码

 

其中调用了runDummySparkJob方法来启动Spark Streaming的框架第一个Job,其中collect这个action操作会触发Spark Job的执行。这个方法是为了确保每个Slave都注册上,避免所有Receiver都在一个节点,使后面的计算能负载均衡。
 
复制代码
/**
 * Run the dummy Spark job to ensure that all slaves have registered. This avoids all the
 * receivers to be scheduled on the same node.
 *
 * TODO Should poll the executor number and wait for executors according to
 * "spark.scheduler.minRegisteredResourcesRatio" and
 * "spark.scheduler.maxRegisteredResourcesWaitingTime" rather than running a dummy job.
 */
private def runDummySparkJob(): Unit = {
  if (!ssc.sparkContext.isLocal) {
    ssc.sparkContext.makeRDD(1 to 50, 50).map(x => (x, 1)).reduceByKey(_ + _, 20).collect()
  }
  assert(getExecutors.nonEmpty)
}
 
复制代码

 

ReceiverTracker.launchReceivers()还调用了endpoint.send(StartAllReceivers(receivers))方法,Rpc消息通信体发送StartAllReceivers消息。
ReceiverTrackerEndpoint它自己接收到消息后,先根据调度策略获得Recevier在哪个Executor上运行,然后在调用startReceiver(receiver, executors)方法,来启动Receiver。
复制代码
override def receive: PartialFunction[Any, Unit] = {
  // Local messages
  case StartAllReceivers(receivers) =>
    val scheduledLocations = schedulingPolicy.scheduleReceivers(receivers, getExecutors)
    for (receiver <- receivers) {
      val executors = scheduledLocations(receiver.streamId)
      updateReceiverScheduledExecutors(receiver.streamId, executors)
      receiverPreferredLocations(receiver.streamId) = receiver.preferredLocation
      startReceiver(receiver, executors)
    }
 
复制代码

 

在startReceiver方法中,ssc.sparkContext.submitJob提交Job的时候传入startReceiverFunc这个方法,因为startReceiverFunc该方法是在Executor上执行的。而在startReceiverFunc方法中实例化ReceiverSupervisorImpl对象,该对象是对Receiver进行管理和监控。这个Job是Spark Streaming框架为我们启动的第二个Job,且一直运行。因为supervisor.awaitTermination()该方法会阻塞等待退出
 
复制代码
/**
 * Start a receiver along with its scheduled executors
 */
private def startReceiver(
    receiver: Receiver[_],
    scheduledLocations: Seq[TaskLocation]): Unit = {
  def shouldStartReceiver: Boolean = {
 
    // ........... 此处省略1万字 (无关代码) , 呵呵哒 .........
 
  // Function to start the receiver on the worker node
  val startReceiverFunc: Iterator[Receiver[_]] => Unit =
    (iterator: Iterator[Receiver[_]]) => {
      if (!iterator.hasNext) {
        throw new SparkException(
          "Could not start receiver as object not found.")
      }
      if (TaskContext.get().attemptNumber() == 0) {
        val receiver = iterator.next()
        assert(iterator.hasNext == false)
        //实例化Receiver监控者
        val supervisor = new ReceiverSupervisorImpl(
          receiver, SparkEnv.get, serializableHadoopConf.value, checkpointDirOption)
        supervisor.start()
        supervisor.awaitTermination()
      } else {
        // It's restarted by TaskScheduler, but we want to reschedule it again. So exit it.
      }
    }
 
  // Create the RDD using the scheduledLocations to run the receiver in a Spark job
  val receiverRDD: RDD[Receiver[_]] =
    if (scheduledLocations.isEmpty) {
      ssc.sc.makeRDD(Seq(receiver), 1)
    } else {
      val preferredLocations = scheduledLocations.map(_.toString).distinct
      ssc.sc.makeRDD(Seq(receiver -> preferredLocations))
    }
 
  receiverRDD.setName(s"Receiver $receiverId")
  ssc.sparkContext.setJobDescription(s"Streaming job running receiver $receiverId")
  ssc.sparkContext.setCallSite(Option(ssc.getStartSite()).getOrElse(Utils.getCallSite()))
  val future = ssc.sparkContext.submitJob[Receiver[_], Unit, Unit](
    receiverRDD, 
   startReceiverFunc, //提交Job时候传入startReceiverFunc这个方法,因为startReceiverFunc该方法是在Executor上执行的
  Seq(0), (_, _) => Unit, ())
 
  // 一直重启 receiver job直到 ReceiverTracker is stopped
  future.onComplete {
    case Success(_) =>
      if (!shouldStartReceiver) {
        onReceiverJobFinish(receiverId)
      } else {
        logInfo(s"Restarting Receiver $receiverId")
        self.send(RestartReceiver(receiver))
      }
    case Failure(e) =>
      if (!shouldStartReceiver) {
        onReceiverJobFinish(receiverId)
      } else {
        logError("Receiver has been stopped. Try to restart it.", e)
        logInfo(s"Restarting Receiver $receiverId")
        self.send(RestartReceiver(receiver))
      }
  }(submitJobThreadPool)
  logInfo(s"Receiver ${receiver.streamId} started")
}
 
复制代码

 

接下来看下ReceiverSupervisorImpl的启动过程,先启动所有注册上的BlockGenerator对象,然后向ReceiverTrackerEndpoint发送RegisterReceiver消息,再调用receiver的onStart方法。
 
/** Start the supervisor */
def start() {
  onStart()
  startReceiver()
}

 

 
其中的onStart():启动所有注册上的BlockGenerator对象
override protected def onStart() {
  registeredBlockGenerators.foreach { _.start() }
}

 

 
其中的startReceiver()方法中调用onReceiverStart()然后再调用receiver的onStart方法。
 
复制代码
/** Start receiver */
def startReceiver(): Unit = synchronized {
  try {
    if (onReceiverStart()) {
      logInfo("Starting receiver")
      receiverState = Started
      receiver.onStart()
      logInfo("Called receiver onStart")
    } else {
      // The driver refused us
      stop("Registered unsuccessfully because Driver refused to start receiver " + streamId, None)
    }
  } catch {
    case NonFatal(t) =>
      stop("Error starting receiver " + streamId, Some(t))
  }
}
复制代码

 

 
在onReceiverStart()中向ReceiverTrackerEndpoint发送RegisterReceiver消息
 
override protected def onReceiverStart(): Boolean = {
  val msg = RegisterReceiver(
    streamId, receiver.getClass.getSimpleName, host, executorId, endpoint)
  trackerEndpoint.askWithRetry[Boolean](msg)
}

 

 
其中在Driver运行的ReceiverTrackerEndpoint对象接收到RegisterReceiver消息后,将streamId, typ, host, executorId, receiverEndpoint封装为ReceiverTrackingInfo保存到内存对象receiverTrackingInfos这个HashMap中。
 
复制代码
override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
  // Remote messages
  case RegisterReceiver(streamId, typ, host, executorId, receiverEndpoint) =>
    val successful =
      registerReceiver(streamId, typ, host, executorId, receiverEndpoint, context.senderAddress)
    context.reply(successful)
 
复制代码

 

 
registerReceiver方法源码:
复制代码
/** Register a receiver */
private def registerReceiver(
    streamId: Int,
    typ: String,
    host: String,
    executorId: String,
    receiverEndpoint: RpcEndpointRef,
    senderAddress: RpcAddress
  ): Boolean = {
  if (!receiverInputStreamIds.contains(streamId)) {
    throw new SparkException("Register received for unexpected id " + streamId)
  }
 
    // ........... 此处省略1万字 (无关代码) , 呵呵哒 .........
 
  if (!isAcceptable) {
    // Refuse it since it's scheduled to a wrong executor
    false
  } else {
    val name = s"${typ}-${streamId}"
    val receiverTrackingInfo = ReceiverTrackingInfo(
      streamId,
      ReceiverState.ACTIVE,
      scheduledLocations = None,
      runningExecutor = Some(ExecutorCacheTaskLocation(host, executorId)),
      name = Some(name),
      endpoint = Some(receiverEndpoint))
    receiverTrackingInfos.put(streamId, receiverTrackingInfo)
    listenerBus.post(StreamingListenerReceiverStarted(receiverTrackingInfo.toReceiverInfo))
    logInfo("Registered receiver for stream " + streamId + " from " + senderAddress)
    true
  }
}
复制代码

 

 
Receiver的启动,以ssc.socketTextStream("localhost", 9999)为例,创建的是SocketReceiver对象。内部启动一个线程来连接Socket Server,读取socket数据并存储。
 
复制代码
private[streaming]
class SocketReceiver[T: ClassTag](
    host: String,
    port: Int,
    bytesToObjects: InputStream => Iterator[T],
    storageLevel: StorageLevel
  ) extends Receiver[T](storageLevel) with Logging {
 
  def onStart() {
    // Start the thread that receives data over a connection
    new Thread("Socket Receiver") {
      setDaemon(true)
      override def run() { receive() }
    }.start()
  }
 
 
  /** Create a socket connection and receive data until receiver is stopped */
  def receive() {
    var socket: Socket = null
    try {
      logInfo("Connecting to " + host + ":" + port)
      socket = new Socket(host, port)
      logInfo("Connected to " + host + ":" + port)
      val iterator = bytesToObjects(socket.getInputStream())
      while(!isStopped && iterator.hasNext) {
        store(iterator.next)
      }
      if (!isStopped()) {
        restart("Socket data stream had no more data")
      } else {
        logInfo("Stopped receiving")
      }
    } catch {
        // ........... 此处省略1万字 (无关代码) , 呵呵哒 .........
  }
}
复制代码

 

 
4.4)接下来看JobScheduler.start()中启动的第四项工作JobGenerator。
JobGenerator有成员RecurringTimer,用于启动消息系统和定时器。按照batchInterval时间间隔定期发送GenerateJobs消息。
 
复制代码
//根据创建StreamContext时传入的batchInterval,定时发送GenerateJobs消息
private val timer = new RecurringTimer(clock, ssc.graph.batchDuration.milliseconds,
  longTime => eventLoop.post(GenerateJobs(new Time(longTime))), "JobGenerator")
 
JobGenerator的start()方法:
/** Start generation of jobs */
def start(): Unit = synchronized {
  if (eventLoop != null) return // generator has already been started
 
  // Call checkpointWriter here to initialize it before eventLoop uses it to avoid a deadlock.
  // See SPARK-10125
  checkpointWriter
 
  eventLoop = new EventLoop[JobGeneratorEvent]("JobGenerator") {
    override protected def onReceive(event: JobGeneratorEvent): Unit = processEvent(event)
 
    override protected def onError(e: Throwable): Unit = {
      jobScheduler.reportError("Error in job generator", e)
    }
  }
 
  // 启动消息循环处理线程
  eventLoop.start()
 
  if (ssc.isCheckpointPresent) {
    restart()
  } else {
    // 开启定时生成Job的定时器
    startFirstTime()
  }
}
复制代码

 

 
JobGenerator.start()中的startFirstTime()的定义:
复制代码
/** Starts the generator for the first time */
private def startFirstTime() {
  val startTime = new Time(timer.getStartTime())
  graph.start(startTime - graph.batchDuration)
  timer.start(startTime.milliseconds)
  logInfo("Started JobGenerator at " + startTime)
}
复制代码

 

JobGenerator.start()中的processEvent()的定义:
  1. /** Processes all events */  
  2. private def processEvent(event: JobGeneratorEvent) {  
  3.   logDebug("Got event " + event)  
  4.   event match {  
  5.     case GenerateJobs(time) =generateJobs(time)  
  6.     case ClearMetadata(time) => clearMetadata(time)  
  7.     case DoCheckpoint(time, clearCheckpointDataLater) =>  
  8.       doCheckpoint(time, clearCheckpointDataLater)  
  9.     case ClearCheckpointData(time) => clearCheckpointData(time)  
  10.   }  
  11. }  
其中generateJobs的定义:
复制代码
/** Generate jobs and perform checkpoint for the given `time`.  */
private def generateJobs(time: Time) {
  // Set the SparkEnv in this thread, so that job generation code can access the environment
  // Example: BlockRDDs are created in this thread, and it needs to access BlockManager
  // Update: This is probably redundant after threadlocal stuff in SparkEnv has been removed.
  SparkEnv.set(ssc.env)
  Try {
 
    // 根据特定的时间获取具体的数据
    jobScheduler.receiverTracker.allocateBlocksToBatch(time) // allocate received blocks to batch
    //调用DStreamGraph的generateJobs生成Job
    graph.generateJobs(time) // generate jobs using allocated block
  } match {
    case Success(jobs) =>
      val streamIdToInputInfos = jobScheduler.inputInfoTracker.getInfo(time)
      jobScheduler.submitJobSet(JobSet(time, jobs, streamIdToInputInfos))
    case Failure(e) =>
      jobScheduler.reportError("Error generating jobs for time " + time, e)
  }
  eventLoop.post(DoCheckpoint(time, clearCheckpointDataLater = false))
}
复制代码

 

 
DStreamGraph的generateJobs方法,调用输出流的generateJob方法来生成Jobs集合。
复制代码
// 输出流:具体Action的输出操作
private val outputStreams = new ArrayBuffer[DStream[_]]()
 
def generateJobs(time: Time): Seq[Job] = {
  logDebug("Generating jobs for time " + time)
  val jobs = this.synchronized {
    outputStreams.flatMap { outputStream =>
      val jobOption = outputStream.generateJob(time)
      jobOption.foreach(_.setCallSite(outputStream.creationSite))
      jobOption
    }
  }
  logDebug("Generated " + jobs.length + " jobs for time " + time)
  jobs
}
复制代码

 

 
来看下DStream的generateJob方法,调用getOrCompute方法来获取当Interval的时候,DStreamGraph会被BatchData实例化成为RDD,如果有RDD则封装jobFunc方法,里面包含context.sparkContext.runJob(rdd, emptyFunc),然后返回封装后的Job。
  1. /**  
  2.  * Generate a SparkStreaming job for the given time. This is an internal method that  
  3.  * should not be called directly. This default implementation creates a job  
  4.  * that materializes the corresponding RDD. Subclasses of DStream may override this  
  5.  * to generate their own jobs.  
  6.  */  
  7. private[streaming] def generateJob(time: Time): Option[Job] = {  
  8.   getOrCompute(time) match {  
  9.     case Some(rdd) => {  
  10.       val jobFunc = () => {  
  11.         val emptyFunc = { (iterator: Iterator[T]) => {} }  
  12.         context.sparkContext.runJob(rdd, emptyFunc)  
  13.       }  
  14.       Some(new Job(time, jobFunc))  
  15.     }  
  16.     case None => None  
  17.   }  
  18. }  
接下来看JobScheduler的submitJobSet方法,向线程池中提交JobHandler。而JobHandler实现了Runnable 接口,最终调用了job.run()这个方法。看一下Job类的定义,其中run方法调用的func为构造Job时传入的jobFunc,其包含了context.sparkContext.runJob(rdd, emptyFunc)操作,最终导致Job的提交。
复制代码
def submitJobSet(jobSet: JobSet) {
  if (jobSet.jobs.isEmpty) {
    logInfo("No jobs added for time " + jobSet.time)
  } else {
    listenerBus.post(StreamingListenerBatchSubmitted(jobSet.toBatchInfo))
    jobSets.put(jobSet.time, jobSet)
    jobSet.jobs.foreach(job => jobExecutor.execute(new JobHandler(job)))
    logInfo("Added jobs for time " + jobSet.time)
  }
}
复制代码

 

 
JobHandler实现了Runnable 接口,最终调用了job.run()这个方法:
复制代码
private class JobHandler(job: Job) extends Runnable with Logging {
    import JobScheduler._
 
    def run() {
      try {
    
         //  *********** 此处省略无关代码 *******************
 
        // We need to assign `eventLoop` to a temp variable. Otherwise, because
        // `JobScheduler.stop(false)` may set `eventLoop` to null when this method is running, then
        // it's possible that when `post` is called, `eventLoop` happens to null.
        var _eventLoop = eventLoop
        if (_eventLoop != null) {
          _eventLoop.post(JobStarted(job, clock.getTimeMillis()))
          // Disable checks for existing output directories in jobs launched by the streaming
          // scheduler, since we may need to write output to an existing directory during checkpoint
          // recovery; see SPARK-4835 for more details.
          PairRDDFunctions.disableOutputSpecValidation.withValue(true) {
            job.run()
          }
          _eventLoop = eventLoop
          if (_eventLoop != null) {
            _eventLoop.post(JobCompleted(job, clock.getTimeMillis()))
          }
        } else {
          // JobScheduler has been stopped.
        }
      } finally {
        ssc.sc.setLocalProperty(JobScheduler.BATCH_TIME_PROPERTY_KEY, null)
        ssc.sc.setLocalProperty(JobScheduler.OUTPUT_OP_ID_PROPERTY_KEY, null)
      }
    }
  }
}
复制代码

 

Job的代码片段:
  1. private[streaming]  
  2. class Job(val time: Time, func: () => _) {  
  3.   private var _id: String = _  
  4.   private var _outputOpId: Int = _  
  5.   private var isSet = false  
  6.   private var _result: Try[_] = null  
  7.   private var _callSite: CallSite = null  
  8.   private var _startTime: Option[Long] = None  
  9.   private var _endTime: Option[Long] = None  
  10.   def run() {  
  11.     _result = Try(func())  
  12.   }  
 
以上是主要源码的分析,累死宝宝了,......慢慢的成就感 
0
0
分享到:
评论

相关推荐

    基于SparkStreaming的实时音乐推荐系统源码.zip

    在本项目中,"基于SparkStreaming的实时音乐推荐系统源码.zip",主要涉及的是如何利用Apache Spark Streaming这一强大的实时处理框架,构建一个能够实时分析用户行为并进行个性化音乐推荐的系统。Spark Streaming是...

    深入理解spark:核心思想与源码分析 高清版本

    1. Spark概述:Spark作为一个分布式计算框架,以其内存计算特性(In-Memory Computing)和高效率著称,广泛应用于大数据处理、机器学习、实时流处理等多个领域。它的设计目标是提供低延迟的数据处理,同时保持易于...

    Spark Streaming Real-time big-data processing

    Spark Streaming能够以微批处理的方式处理数据流,将实时数据流分解成小的时间窗口(称为DStream,Discretized Stream),然后应用Spark的计算模型进行处理。 **DStream:离散化数据流** DStream是Spark Streaming...

    spark大数据商业实战三部曲源码及资料.zip

    源码分析有助于深入理解Spark的内部工作原理,例如: - DAGScheduler如何将作业拆分成Stage,Stage再拆分成Task。 - TaskScheduler如何将Task分配到Executor上执行。 - Shuffle过程是如何实现的,包括...

    深入理解Spark核心思想与源码分析

    Spark作为一个分布式计算框架,以其高效、易用和多模态处理能力在大数据处理领域备受青睐。以下是关于Spark的一些关键知识点的详细说明: **1. Spark概述** Spark是由加州大学伯克利分校AMPLab开发的大数据处理框架...

    深入理解Spark:核心思想及源码分析.pdf

    《深入理解Spark:核心思想及源码分析》这本书旨在帮助读者深入掌握Apache Spark这一大数据处理框架的核心原理与实现细节。Spark作为一个快速、通用且可扩展的数据处理系统,已经在大数据领域得到了广泛应用。它提供...

    深入理解Spark 核心思想与源码分析

    8. **Spark Streaming**:Spark Streaming构建在微批处理之上,通过将流数据划分为小批次处理,实现了低延迟的实时流处理。 9. **MLlib与Spark ML**:Spark提供了机器学习库MLlib,以及基于DataFrame的ML,支持各种...

    Spark-2.4.5官网下载源码包

    5. `graphx/`:GraphX图计算框架,用于创建、操作和分析图形数据。 6. `examples/`:包含各种示例程序,展示了Spark的基本用法和功能。 7. `build/`:构建脚本和配置文件,如`build.gradle`,用于编译Spark源码。 8....

    毕业设计:基于Spark streaming的系统日志分析系统.zip

    Spark Streaming是Apache Spark框架的一个扩展,它允许实时处理连续的数据流,提供了强大的流处理能力。 【描述】"基于spark开发的完整项目算法源码,可用于毕业设计、课程设计、练手学习等" 这个项目包含了完整的...

    spark源码分析

    《Spark源码分析》 Spark,作为大数据处理领域的重要框架,以其高效、易用和弹性伸缩等特性,被广泛应用于数据处理、机器学习和实时流处理等多个场景。本资料将深入探讨Spark的核心思想和源码,帮助读者从底层原理...

    Flume push数据到SparkStreaming

    标题中的“Flume push数据到SparkStreaming”是指在大数据处理领域中,使用Apache Flume将实时数据流推送到Apache Spark Streaming进行进一步的实时分析和处理的过程。这两个组件都是Apache Hadoop生态系统的重要...

    深入理解Spark+核心思想与源码分析

    5. **Spark Streaming**:Spark Streaming支持实时流处理,通过微批处理的方式实现低延迟的数据处理。 6. **Spark MLlib**:MLlib是Spark的机器学习库,包含了多种常见的机器学习算法,如分类、回归、聚类等,同时...

    spark源码结构,源码说明

    Spark作为一个流行的分布式计算框架,其源码结构复杂且深奥,但理解它对于深入学习和优化Spark应用至关重要。源码结构主要分为几个关键部分,包括核心库、模块化组件以及插件机制等。以下是对Spark源码结构的详细...

    spark-2.4.0源码

    3. **Spark Streaming**:提供了实时流处理能力,它将数据流划分为微批次,然后用Spark Core进行处理。这种方式结合了实时处理和批处理的优点,保证了低延迟的同时也具备高吞吐量。 4. **MLlib**:Spark的机器学习...

    基于spark streaming和kafka,hbase的日志统计分析系统.zip

    Spark Streaming是Apache Spark框架的一个模块,专门用于处理实时数据流。它提供了一个高级抽象,使得开发人员能够轻松地处理持续的数据流,同时保持Spark核心的弹性、容错性和高吞吐量特性。Spark Streaming通过将...

    spark 优秀资源源码(个人整理)

    Spark是Apache基金会下的一个分布式计算框架,以其高效、易用和可扩展性而著名。它在大数据处理领域扮演着重要角色,尤其在实时处理、批处理以及机器学习等任务上表现出色。本资源集合中包含了Spark与ETL(数据提取...

    基于Spark Streaming+ALS的餐饮智能推荐系统.zip

    Spark Streaming通过DStream(Discretized Stream)抽象,将连续的数据流分解为一系列小批次的批处理任务,从而在Spark的计算框架上高效运行。 其次,ALS是协同过滤算法的一种,常用于推荐系统中。它假设用户和商品...

    spark-2.2.0.tgz源码

    Spark是Apache软件基金会下的一个开源大数据处理框架,其2.2.0版本是该框架的一个重要里程碑。这个版本包含了丰富的功能改进和性能优化,对于理解Spark的工作原理、开发分布式应用程序或者进行性能调优来说,研究...

    基于flume+kafka_spark streaming+hbase的流式处理系统设计与实现.zip

    4. **Spark Core**: Spark的核心组件,提供了分布式计算框架,支持内存计算,显著提升了数据处理速度。Spark Streaming构建在Spark Core之上,共享了Spark的高效计算能力。 5. **HBase**: HBase是一个分布式的、...

Global site tag (gtag.js) - Google Analytics