版本定制第5课:基于案例一节课贯通Spark Streaming流计算框架的运行源码

本期内容:
1、在线动态计算分类最热门商品案例回顾与演示
2、基于案例贯通Spark Streaming的运行源码
第一部分案例:
package com.dt.spark.sparkstreaming

import com.robinspark.utils.ConnectionPool
import org.apache.spark.SparkConf
import org.apache.spark.sql.Row
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.sql.types.{IntegerType StringType StructField StructType}
import org.apache.spark.streaming.{Seconds StreamingContext}

/**
  *  使用 Spark Streaming+Spark SQL 来在线动态计算电商中不同类别中最热门的商品排名,例如手机这个类别下面最热门的三种手机、电视这个类别
   下最热门的三种电视,该实例在实际生产环境下具有非常重大的意义;
   *
  *  @author  DT 大数据梦工厂
   新浪微博: http://weibo.com/ilovepains/
  *
  *
  *    实现技术: Spark Streaming+Spark SQL ,之所以 Spark Streaming 能够使用 ML sql graphx 等功能是因为有 foreachRDD Transform
  *  等接口,这些接口中其实是基于 RDD 进行操作,所以以 RDD 为基石,就可以直接使用 Spark 其它所有的功能,就像直接调用 API 一样简单。
   *   假设说这里的数据的格式: user item category ,例如 Rocky Samsung Android
  */
object OnlineTheTop3ItemForEachCategory2DB {
   def  main(args: Array[ String]){
     /**
      *  1 步:创建 Spark 的配置对象 SparkConf ,设置 Spark 程序的运行时的配置信息,
       例如说通过 setMaster 来设置程序要链接的 Spark 集群的 Master URL, 如果设置
       local ,则代表 Spark 程序在本地运行,特别适合于机器配置条件非常差(例如
       只有 1G 的内存)的初学者        *
      */
     val conf =  new SparkConf()  // 创建 SparkConf 对象
    conf.setAppName( "OnlineTheTop3ItemForEachCategory2DB"// 设置应用程序的名称,在程序运行的监控界面可以看到名称
    conf.setMaster( "spark://Master:7077"// 此时,程序在 Spark 集群
     //conf.setMaster("local[2]")
    // 设置 batchDuration 时间间隔来控制 Job 生成的频率并且创建 Spark Streaming 执行的入口
     val ssc =  new StreamingContext(conf Seconds( 5))

    ssc.checkpoint( "/root/Documents/SparkApps/checkpoint")

     val userClickLogsDStream = ssc.socketTextStream( "Master" 9999)

     val formattedUserClickLogsDStream = userClickLogsDStream.map(clickLog =>
        (clickLog.split( " ")( 2) +  "_" + clickLog.split( " ")( 1) 1))

//    val categoryUserClickLogsDStream = formattedUserClickLogsDStream.reduceByKeyAndWindow((v1:Int, v2: Int) => v1 + v2,
//      (v1:Int, v2: Int) => v1 - v2, Seconds(60), Seconds(20))

     val categoryUserClickLogsDStream = formattedUserClickLogsDStream.reduceByKeyAndWindow(_+_ ,
      _-_ Seconds( 60) Seconds( 20))

    categoryUserClickLogsDStream.foreachRDD { rdd => {
       if (rdd.isEmpty()) {
         println( "No data inputted!!!")
      }  else {
         val categoryItemRow = rdd.map(reducedItem => {
           val category = reducedItem._1.split( "_")( 0)
           val item = reducedItem._1.split( "_")( 1)
           val click_count = reducedItem._2
           Row(category item click_count)
        })

         val structType =  StructType( Array(
           StructField( "category" StringType true) ,
           StructField( "item" StringType true) ,
           StructField( "click_count" IntegerType true)
        ))

         val hiveContext =  new HiveContext(rdd.context)
         val categoryItemDF = hiveContext.createDataFrame(categoryItemRow structType)

        categoryItemDF.registerTempTable( "categoryItemTable")

         val reseltDataFram = hiveContext.sql( "SELECT category,item,click_count FROM (SELECT category,item,click_count,row_number()" +
           " OVER (PARTITION BY category ORDER BY click_count DESC) rank FROM categoryItemTable) subquery " +
           " WHERE rank <= 3")
        reseltDataFram.show()

         val resultRowRDD = reseltDataFram. rdd

        resultRowRDD.foreachPartition { partitionOfRecords => {

           if (partitionOfRecords.isEmpty){
             println( "This RDD is not null but partition is null")
          }  else {
             // ConnectionPool is a static, lazily initialized pool of connections
             val connection = ConnectionPool. getConnection()
            partitionOfRecords.foreach(record => {
               val sql =  "insert into categorytop3(category,item,client_count) values('" + record.getAs( "category") +  "','" +
                record.getAs( "item") +  "'," + record.getAs( "click_count") +  ")"
               val stmt = connection.createStatement() ;
              stmt.executeUpdate(sql) ;

            })
            ConnectionPool. returnConnection(connection)  // return to the pool for future reuse

          }
        }
        }
      }
    }
    }
     /**
      *  StreamingContext 调用 start 方法的内部其实是会启动 JobScheduler Start 方法,进行消息循环,在 JobScheduler
      *  start 内部会构造 JobGenerator ReceiverTacker ,并且调用 JobGenerator ReceiverTacker start 方法:
       *   1 JobGenerator 启动后会不断的根据 batchDuration 生成一个个的 Job
      *   2 ReceiverTracker 启动后首先在 Spark Cluster 中启动 Receiver (其实是在 Executor 中先启动 ReceiverSupervisor ),在 Receiver 收到
       *    数据后会通过 ReceiverSupervisor 存储到 Executor 并且把数据的 Metadata 信息发送给 Driver 中的 ReceiverTracker ,在 ReceiverTracker
      *    内部会通过 ReceivedBlockTracker 来管理接受到的元数据信息
       每个 BatchInterval 会产生一个具体的 Job ,其实这里的 Job 不是 Spark Core 中所指的 Job ,它只是基于 DStreamGraph 而生成的 RDD
      *  DAG 而已,从 Java 角度讲,相当于 Runnable 接口实例,此时要想运行 Job 需要提交给 JobScheduler ,在 JobScheduler 中通过线程池的方式找到一个
       单独的线程来提交 Job 到集群运行(其实是在线程中基于 RDD Action 触发真正的作业的运行),为什么使用线程池呢?
       *   1 ,作业不断生成,所以为了提升效率,我们需要线程池;这和在 Executor 中通过线程池执行 Task 有异曲同工之妙;
       *   2 ,有可能设置了 Job FAIR 公平调度的方式,这个时候也需要多线程的支持;
       *
      */
    ssc.start()
    ssc.awaitTermination()
  }
}
第二部分源码解析:

1、构建StreamingContext时传递SparkConf参数(或者自己Configuration)在内部创建SparkContext
def this(conf: SparkConf batchDuration: Duration) = {
   this(StreamingContext. createNewSparkContext(conf) null batchDuration)
}

2、事实说明SparkStreaming就是SparkCore上的一个应用程序
private[streaming]  def  createNewSparkContext(conf: SparkConf): SparkContext = {
   new SparkContext(conf)
}

3、创建Socket输入流
def socketTextStream(
    hostname:  String ,
    port:  Int,
    storageLevel: StorageLevel = StorageLevel. MEMORY_AND_DISK_SER_2
  ): ReceiverInputDStream[ String] = withNamedScope( "socket text stream") {
  socketStream[ String](hostname port SocketReceiver. bytesToLines storageLevel)
}

4、创建SocketInputDStream
def  socketStream[ T: ClassTag](
    hostname:  String ,
    port:  Int,
    converter: (InputStream) =>  Iterator[ T] ,
    storageLevel: StorageLevel
  ): ReceiverInputDStream[ T] = {
   new SocketInputDStream[ T]( this hostname port converter storageLevel)
}

5、SocketInputDstream继承ReceiverInputDStream,通过构建Receiver来接收数据
private[streaming]
class SocketInputDStream[ T: ClassTag](
    ssc_ : StreamingContext ,
    host:  String ,
    port:  Int,
    bytesToObjects: InputStream =>  Iterator[ T] ,
    storageLevel: StorageLevel
  )  extends ReceiverInputDStream[ T](ssc_) {

   def  getReceiver(): Receiver[ T] = {
     new SocketReceiver(host port bytesToObjects storageLevel)
  }
}

5.1、ReceiverInputDStream
abstract class ReceiverInputDStream[ T: ClassTag](ssc_ : StreamingContext)
   extends InputDStream[ T](ssc_) {

abstract class InputDStream[ T: ClassTag] (ssc_ : StreamingContext)
   extends DStream[ T](ssc_) {

5.2、DStream
/*
* DStreams internally is characterized by a few basic properties:
*  - A list of other DStreams that the DStream depends on
*  - A time interval at which the DStream generates an RDD
*  - A function that is used to generate an RDD after each time interval
*/
     1)、依赖于其他DStream
     2)、什么时候依据DStream,依赖关系的模板,构成RDD之间的依赖
     3)、基于DStream它有一个Function,Function 基于Batch Interval(time Interval)生成RDD,这个和定时器有关系

abstract class DStream[ T: ClassTag] (
     @transient  private[streaming]  var ssc: StreamingContext
extends Serializable  with Logging {

6、SocketReceiver对象在onStart中创建Thread启动run方法调用执行receive接收数据。
   def  onStart() {
     // Start the thread that receives data over a connection
     new Thread( "Socket Receiver") {
      setDaemon( true)
       override def  run() { receive() }
    }.start()
  }

7、创建一个Socket connection连接接收数据
   /** 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")
      }

8、 总体流程:在StreamingContext调用start方法的内部其实是会启动JobScheduler的Start方法,进行消息循环,在JobScheduler的start内部会构造JobGenerator和ReceiverTacker,并且调用JobGenerator和ReceiverTacker的start方法:
     1)、JobGenerator启动后会不断的根据batchDuration生成一个个的Job
     2)、ReceiverTracker启动后首先在Spark Cluster中启动Receiver(其实是在Executor中先启动ReceiverSupervisor),
在Receiver收到 数据后会通过ReceiverSupervisor存储到Executor并且把数据的Metadata信息发送给Driver中的ReceiverTracker,
在ReceiverTracker 内部会通过ReceivedBlockTracker来管理接受到的元数据信息 每个BatchInterval会产生一个具体的Job(这里的Job主要是封装了业务逻辑例如上面实例中的代码),其实这里的Job不是Spark Core中所指的Job,它只是基于DStreamGraph而生成的RDD 的DAG而已,
从Java角度讲,相当于Runnable接口实例,此时要想运行Job需要提交给JobScheduler,在JobScheduler中通过线程池的方式找到一个 单独的线程来提交Job到集群运行(其实是在线程中基于RDD的Action触发真正的作业的运行),
     为什么使用线程池呢?
          a)、作业不断生成,所以为了提升效率,我们需要线程池;这和在Executor中通过线程池执行Task有异曲同工之妙;
          b)、有可能设置了Job的FAIR公平调度的方式,这个时候也需要多线程的支持;
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
8.1、StreamingContext.start
// Start the streaming scheduler in a new thread, so that thread local properties
// like call sites and job groups can be reset without affecting those of the
// current thread.
ThreadUtils. runInNewThread( "streaming-start") {
  sparkContext.setCallSite( startSite.get)
  sparkContext.clearJobGroup()
  sparkContext.setLocalProperty(SparkContext. SPARK_JOB_INTERRUPT_ON_CANCEL "false")
   scheduler.start()
}

补充:线程本地存储,线程ThreadLocal每个线程有自己的私有属性,设置线程的私有属性不会影响当前线程或其他线程
9、JobScheduler.start 创建EventLoop消息线程并启动
def  start():  Unit = synchronized {
   if ( eventLoop !=  nullreturn  // scheduler has already been started

  logDebug( "Starting JobScheduler")
   eventLoop new EventLoop[JobSchedulerEvent]( "JobScheduler") {
     override protected def  onReceive(event: JobSchedulerEvent):  Unit = processEvent(event)

     override protected def  onError(e:  Throwable):  Unit = reportError( "Error in job scheduler" e)
  }
   eventLoop.start()

9.1、EventLoop中创建Thread线程接收和发送消息,调用JobScheduler中的processEvent方法
private[spark]  abstract class EventLoop[ E](name:  Stringextends Logging {

   private val  eventQueue: BlockingQueue[ E] =  new LinkedBlockingDeque[ E]()

   private val  stopped new AtomicBoolean( false)

   private val  eventThread new Thread(name) {
    setDaemon( true)

     override def  run():  Unit = {
       try {
         while (! stopped.get) {
           val event =  eventQueue.take()
           try {
            onReceive(event)
          }  catch {

9.2、会接受不同的任务,JobScheduler是整个Job的调度器,它本身用了一个线程循环,去监听不同的Job启动、Job完成、Job失败等任务(消息驱动系统)
private def  processEvent(event: JobSchedulerEvent) {
   try {
    event  match {
       case  JobStarted(job startTime) => handleJobStart(job startTime)
       case  JobCompleted(job completedTime) => handleJobCompletion(job completedTime)
       case  ErrorReported(m e) => handleError(m e)
    }
  }  catch {

10、JobScheduler.start
// attach rate controllers of input streams to receive batch completion updates
for {
  inputDStream <- ssc. graph.getInputStreams
  rateController <- inputDStream. rateController
} ssc.addStreamingListener(rateController)

10.1、多个InputStream
inputDStream <- ssc. graph.getInputStreams

10.2、RateController控制输入的速度
// Keep track of the freshest rate for this stream using the rateEstimator
protected[streaming]  val  rateController: Option[RateController] = None

11、JobScheduler.start
listenerBus.start(ssc.sparkContext)
receiverTracker new ReceiverTracker(ssc)
inputInfoTracker new InputInfoTracker(ssc)
receiverTracker.start()
jobGenerator.start()

11.1、StreamingListenerBus
override def  onPostEvent(listener: StreamingListener event: StreamingListenerEvent):  Unit = {
  event  match {
     case receiverStarted: StreamingListenerReceiverStarted =>
      listener.onReceiverStarted(receiverStarted)
     case receiverError: StreamingListenerReceiverError =>
      listener.onReceiverError(receiverError)
     case receiverStopped: StreamingListenerReceiverStopped =>
      listener.onReceiverStopped(receiverStopped)
     case batchSubmitted: StreamingListenerBatchSubmitted =>
      listener.onBatchSubmitted(batchSubmitted)
     case batchStarted: StreamingListenerBatchStarted =>
      listener.onBatchStarted(batchStarted)
     case batchCompleted: StreamingListenerBatchCompleted =>
      listener.onBatchCompleted(batchCompleted)
     case outputOperationStarted: StreamingListenerOutputOperationStarted =>
      listener.onOutputOperationStarted(outputOperationStarted)
     case outputOperationCompleted: StreamingListenerOutputOperationCompleted =>
      listener.onOutputOperationCompleted(outputOperationCompleted)
     case _ =>
  }
}

11.2、receiverTracker.start(),ReceiveTracker是通过发Job的方式到集群的Executor上启动Receiver
/** Start the endpoint and receiver execution thread. */
def  start():  Unit = synchronized {
   if (isTrackerStarted) {
     throw new SparkException( "ReceiverTracker already started")
  }

   if (! receiverInputStreams.isEmpty) {
     endpoint = ssc. env.rpcEnv.setupEndpoint(
       "ReceiverTracker" new ReceiverTrackerEndpoint(ssc. env.rpcEnv))
     if (!skipReceiverLaunch) launchReceivers()
    logInfo( "ReceiverTracker started")
     trackerState Started
  }
}

11.2.1、创建一个ReceiverTrackerEndpoint消息通信体
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)
    }
   case  RestartReceiver(receiver) =>
     // Old scheduled executors minus the ones that are not active any more
     val oldScheduledExecutors = getStoredScheduledExecutors(receiver.streamId)
     val scheduledLocations =  if (oldScheduledExecutors.nonEmpty) {
         // Try global scheduling again
        oldScheduledExecutors
      }  else {
         val oldReceiverInfo =  receiverTrackingInfos(receiver.streamId)
         // Clear "scheduledLocations" to indicate we are going to do local scheduling
         val newReceiverInfo = oldReceiverInfo.copy(
          state = ReceiverState. INACTIVE scheduledLocations = None)
         receiverTrackingInfos(receiver.streamId) = newReceiverInfo
         schedulingPolicy.rescheduleReceiver(
          receiver.streamId ,
          receiver.preferredLocation ,
           receiverTrackingInfos ,
          getExecutors)
      }
     // Assume there is one receiver restarting at one time, so we don't need to update
    // receiverTrackingInfos
    startReceiver(receiver scheduledLocations)
 
11.2.1.1、ReceiverSchedulingPolicy.scheduleReceivers,从下面的代码中可以看出来在那些Executor上启动Receiver,以及怎么具体在Executor上启动Receiver
// Firstly, we need to respect "preferredLocation". So if a receiver has "preferredLocation",
// we need to make sure the "preferredLocation" is in the candidate scheduled executor list.
for (i <-  until receivers.length) {
   // Note: preferredLocation is host but executors are host_executorId
  receivers(i).preferredLocation.foreach { host =>
    hostToExecutors.get(host)  match {
       case  Some(executorsOnHost) =>
         // preferredLocation is a known host. Select an executor that has the least receivers in
        // this host
         val leastScheduledExecutor =
          executorsOnHost.minBy(executor => numReceiversOnExecutor(executor))
        scheduledLocations(i) += leastScheduledExecutor
        numReceiversOnExecutor(leastScheduledExecutor) =
          numReceiversOnExecutor(leastScheduledExecutor) +  1
       case None =>
         // preferredLocation is an unknown host.
        // Note: There are two cases:
        // 1. This executor is not up. But it may be up later.
        // 2. This executor is dead, or it's not a host in the cluster.
        // Currently, simply add host to the scheduled executors.

        // Note: host could be `HDFSCacheTaskLocation`, so use `TaskLocation.apply` to handle
        // this case
        scheduledLocations(i) +=  TaskLocation(host)
    }
  }
}

补充:ReceiverTracker本身不直接监管Receiver,它是Driver级别的可间接地,用ReceiverSupervisor监控那台机器上Executor中的Receiver。

11.2.2、if (!skipReceiverLaunch) 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))
}

11.2.2.1运行了一个Dummy的作业,确保所有的Slaves正常工作,保证所有的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( to  50 50).map(x => (x 1)).reduceByKey(_ + _ 20).collect()
  }
   assert(getExecutors.nonEmpty)
}

11.2.2.2、 endpoint .send(StartAllReceivers(receivers)
// endpoint is created when generator starts.
// This not being null means the tracker has been started and not stopped
private var  endpoint: RpcEndpointRef =  null

endpoint = ssc. env.rpcEnv.setupEndpoint(
   "ReceiverTracker" new ReceiverTrackerEndpoint(ssc. env.rpcEnv))

ReceiverTrackerEndpoint
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
// 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)
       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 Seq( 0) (_ _) =>  Unit, ())
   // We will keep restarting the receiver job until 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.startReceiver
/** 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)
    }

override protected def  onReceiverStart():  Boolean = {
   val msg =  RegisterReceiver(
     streamId receiver.getClass.getSimpleName host executorId endpoint)
   trackerEndpoint.askWithRetry[ Boolean](msg)
}

11.3、JobScheduler.start   jobGenerator.start()
/** Start generation of jobs */
def  start():  Unit = synchronized {
   if ( eventLoop !=  nullreturn  // 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 {
    startFirstTime()
  }
}

根据时间间隔不断发送消息
/** Processes all events */
private def  processEvent(event: JobGeneratorEvent) {
  logDebug( "Got event " + event)
  event  match {
     case  GenerateJobs(time) => generateJobs(time)
     case  ClearMetadata(time) => clearMetadata(time)
     case  DoCheckpoint(time clearCheckpointDataLater) =>
      doCheckpoint(time clearCheckpointDataLater)
     case  ClearCheckpointData(time) => clearCheckpointData(time)
  }
}

/** 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
     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))
}

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)
  }
}

/**
 * Executes the given task sometime in the future.  The task
 * may execute in a new thread or in an existing pooled thread.
 *
 * If the task cannot be submitted for execution, either because this
 * executor has been shutdown or because its capacity has been reached,
 * the task is handled by the current { @code  RejectedExecutionHandler}.
 *
 *  @param  command  the task to execute
 *  @throws  RejectedExecutionException at discretion of
 *         { @code  RejectedExecutionHandler}, if the task
 *         cannot be accepted for execution
 *  @throws  NullPointerException if { @code  command} is null
 */
public void  execute(Runnable command) {
     if (command ==  null)
         throw new NullPointerException() ;
     /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
     int c =  ctl.get() ;
    if ( workerCountOf(c) <  corePoolSize) {
         if (addWorker(command , true))
             return;
        c =  ctl.get() ;
    }
     if ( isRunning(c) &&  workQueue.offer(command)) {
         int recheck =  ctl.get() ;
        if (!  isRunning(recheck) && remove(command))
            reject(command) ;
        else if ( workerCountOf(recheck) ==  0)
            addWorker( null, false) ;
    }
     else if (!addWorker(command , false))
        reject(command) ;
}

   private class JobHandler(job: Job)  extends Runnable  with Logging {
     import JobScheduler._

     def  run() {
       try {
         val formattedTime = UIUtils. formatBatchTime(
          job.time.milliseconds ssc. graph. batchDuration.milliseconds showYYYYMMSS =  false)
         val batchUrl =  s"/streaming/batch/?id= ${job.time.milliseconds} "
         val batchLinkText =  s"[output operation  ${job.outputOpId} , batch time  ${formattedTime} ]"

        ssc. sc.setJobDescription(
           s"""Streaming job from <a href=" $batchUrl "> $batchLinkText </a>""")
        ssc. sc.setLocalProperty( BATCH_TIME_PROPERTY_KEY job.time.milliseconds.toString)
        ssc. sc.setLocalProperty( OUTPUT_OP_ID_PROPERTY_KEY job.outputOpId.toString)

         // 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)
      }
    }
  }
}

private def  processEvent(event: JobSchedulerEvent) {
   try {
    event  match {
       case  JobStarted(job startTime) => handleJobStart(job startTime)
       case  JobCompleted(job completedTime) => handleJobCompletion(job completedTime)
       case  ErrorReported(m e) => handleError(m e)
    }
  }  catch {
     case e:  Throwable =>
      reportError( "Error in job scheduler" e)
  }
}
 
private def  handleJobStart(job: Job startTime:  Long) {
   val jobSet =  jobSets.get(job.time)
   val isFirstJobOfJobSet = !jobSet.hasStarted
  jobSet.handleJobStart(job)
   if (isFirstJobOfJobSet) {
     // "StreamingListenerBatchStarted" should be posted after calling "handleJobStart" to get the
    // correct "jobSet.processingStartTime".
     listenerBus.post( StreamingListenerBatchStarted(jobSet.toBatchInfo))
  }
  job.setStartTime(startTime)
   listenerBus.post( StreamingListenerOutputOperationStarted(job.toOutputOperationInfo))
  logInfo( "Starting job " + job.id +  " from job set of time " + jobSet.time)
}

   private class JobHandler(job: Job)  extends Runnable  with Logging {
     import JobScheduler._

     def  run() {
       try {
         val formattedTime = UIUtils. formatBatchTime(
          job.time.milliseconds ssc. graph. batchDuration.milliseconds showYYYYMMSS =  false)
         val batchUrl =  s"/streaming/batch/?id= ${job.time.milliseconds} "
         val batchLinkText =  s"[output operation  ${job.outputOpId} , batch time  ${formattedTime} ]"

        ssc. sc.setJobDescription(
           s"""Streaming job from <a href=" $batchUrl "> $batchLinkText </a>""")
        ssc. sc.setLocalProperty( BATCH_TIME_PROPERTY_KEY job.time.milliseconds.toString)
        ssc. sc.setLocalProperty( OUTPUT_OP_ID_PROPERTY_KEY job.outputOpId.toString)

         // 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)
      }
    }
  }
}


资料来源于:王家林(Spark版本定制班课程)
新浪微博:http://www.weibo.com/ilovepains


你可能感兴趣的:(版本定制第5课:基于案例一节课贯通Spark Streaming流计算框架的运行源码)