第5課:基於案例一節課貫通Spark Streaming流計算框架的運行源碼

Spark Streaming的Job到底是如何運行的,我們下面以一個例子來解析一下:

package com.dt.spark.streaming

import com.dt.spark.common.ConnectPool
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}


/**
 * 以網站熱詞排名爲例,將處理結果寫到MySQL中
 * Created by dinglq on 2016/5/3.
 */
object WriteDataToMySQL {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("WriteDataToMySQL")
    val ssc = new StreamingContext(conf,Seconds(5))
    // 假設socket輸入的數據格式爲:searchKeyword,time
    val ItemsStream = ssc.socketTextStream("spark-master",9999)
    // 將輸入數據變成(searchKeyword,1)
    var ItemPairs = ItemsStream.map(line =>(line.split(",")(0),1))

     val ItemCount = ItemPairs.reduceByKeyAndWindow((v1:Int,v2:Int)=> v1+v2,Seconds(60),Seconds(10))
    //ssc.checkpoint("/user/checkpoints/")
    // val ItemCount = ItemPairs.reduceByKeyAndWindow((v1:Int,v2:Int)=> v1+v2,(v1:Int,v2:Int)=> v1-v2,Seconds(60),Seconds(10))
    /**
     * 接下來需要對熱詞的頻率進行排序,而DStream沒有提供sort的方法。那麼我們可以實現transform函數,用RDD的sortByKey實現
     */
    val hottestWord = ItemCount.transform(itemRDD => {
      val top3 = itemRDD.map(pair => (pair._2, pair._1))
        .sortByKey(false).map(pair => (pair._2, pair._1)).take(3)
      ssc.sparkContext.makeRDD(top3)
    })

    hottestWord.foreachRDD(rdd => {
      rdd.foreachPartition(partitionOfRecords =>{
        val conn = ConnectPool.getConnection
        conn.setAutoCommit(false);  //設爲手動提交
        val  stmt = conn.createStatement();

        partitionOfRecords.foreach( record => {

          stmt.addBatch("insert into searchKeyWord (insert_time,keyword,search_count) values (now(),'"+record._1+"','"+record._2+"')");
        })

        stmt.executeBatch();
        conn.commit();  //提交事務

      })
    })

    ssc.start()
    ssc.awaitTermination()
    ssc.stop()

  }
}


將代碼提交至Spark 集羣運行:

1.程序最初會初始化StreamingContext

def this(conf: SparkConf, batchDuration: Duration) = {
  this(StreamingContext.createNewSparkContext(conf), null, batchDuration)
}

在StreamingContext的構造方法中會新建一個SparkContext實例,從這點也可以說明Streaming是運行在Spark Core 之上的。

StreamingContext初始化的過程中會做如下事情

2.構造DStreamGraph

private[streaming] val graph: DStreamGraph = {
  if (isCheckpointPresent) {
    cp_.graph.setContext(this)
    cp_.graph.restoreCheckpointData()
    cp_.graph
  } else {
    require(batchDur_ != null, "Batch duration for StreamingContext cannot be null")
    val newGraph = new DStreamGraph()
    newGraph.setBatchDuration(batchDur_)
    newGraph
  }
}

3.構造JobScheduler對象

private[streaming] val scheduler = new JobScheduler(this)

而在JobScheduler對象初始化的過程會構造如下對象:JobGenerator、StreamingListenerBus

4.構造JobGenerator對象(JobScheduler.scala的第50行)

private val jobGenerator = new JobGenerator(this)

5.而JobGenerator在實例化時,則會構造一個RecurringTimer(JobGenerator.scala的第58行)

private val timer = new RecurringTimer(clock, ssc.graph.batchDuration.milliseconds,
  longTime => eventLoop.post(GenerateJobs(new Time(longTime))), "JobGenerator")

6.構造StreamingListenerBus對象(JobScheduler.scala的第52行)

val listenerBus = new StreamingListenerBus()

到此,StreamingContext實例化的工作完成(以上只是說明了主要的對象生成,並不完整,請參考源代碼)

7.定義輸入流

val ItemsStream = ssc.socketTextStream("spark-master",9999)

8.此方法會生成一個SocketInputDStream

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

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

SocketInputDStream的繼承關係如下圖:

wKiom1ctbzGAIWN2AAA33794RKw615.png

9.在InputDStream的構造過程中,會將此輸入流SocketInputDStream添加到DStreamGraph的inputStreams數據結構中(InputDStream.scala的第47行

ssc.graph.addInputStream(this)

並且InputDStream和和DStreamGraph實例相互引用(DStreamGraph的第83行)

def addInputStream(inputStream: InputDStream[_]) {
  this.synchronized {
    inputStream.setGraph(this)
    inputStreams += inputStream
  }
}

10.在ReceiverInputDStream構建的過程中會初始化一個ReceiverRateController

override protected[streaming] val rateController: Option[RateController] = {
  if (RateController.isBackPressureEnabled(ssc.conf)) {
    Some(new ReceiverRateController(id, RateEstimator.create(ssc.conf, ssc.graph.batchDuration)))
  } else {
    None
  }
}


特別說明:在DStreamGraph中有個outputStreams,表示SparkStreaming程序的輸出流,在需要數據輸出時,例如print(最終也會調用foreachRDD方法),foreachRDD等都會講此DStream註冊給outputStreams。(DStream.scala的第684行)

private def foreachRDD(
    foreachFunc: (RDD[T], Time) => Unit,
    displayInnerRDDOps: Boolean): Unit = {
  new ForEachDStream(this,
    context.sparkContext.clean(foreachFunc, false), displayInnerRDDOps).register()
}

這裏的context就是StreamingContext。

11.將DStream註冊給DStreamGraph(DStream.scala的第969行)

private[streaming] def register(): DStream[T] = {
  ssc.graph.addOutputStream(this)
  this
}


所以Streaming程序的整個業務代碼,就是將InputDStream經過各種轉換計算變成OutputDStream的過程。

12. StreamingContext啓動

ssc.start()

啓動過程中,會判斷StreamingContext的狀態,它有三個狀態INITIALIZED、ACTIVE、STOP。只有狀態爲INITAILIZED才允許啓動。代碼如下

StreamingContext.scala的第594行

def start(): Unit = synchronized {
  state match {
    case INITIALIZED =>
      startSite.set(DStream.getCreationSite())
      StreamingContext.ACTIVATION_LOCK.synchronized {
        StreamingContext.assertNoOtherContextIsActive()
        try {
          validate()

          // 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()
          }
          state = StreamingContextState.ACTIVE
        } catch {
          case NonFatal(e) =>
            logError("Error starting the context, marking it as stopped", e)
            scheduler.stop(false)
            state = StreamingContextState.STOPPED
            throw e
        }
        StreamingContext.setActiveContext(this)
      }
      shutdownHookRef = ShutdownHookManager.addShutdownHook(
        StreamingContext.SHUTDOWN_HOOK_PRIORITY)(stopOnShutdown)
      // Registering Streaming Metrics at the start of the StreamingContext
      assert(env.metricsSystem != null)
      env.metricsSystem.registerSource(streamingSource)
      uiTab.foreach(_.attach())
      logInfo("StreamingContext started")
    case ACTIVE =>
      logWarning("StreamingContext has already been started")
    case STOPPED =>
      throw new IllegalStateException("StreamingContext has already been stopped")
  }
}

13.調用JobScheduler的start方法(scheduler.start())

JobScheduler.scala的第62行

def start(): Unit = synchronized {
  if (eventLoop != null) return // 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()

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

  listenerBus.start(ssc.sparkContext)
  receiverTracker = new ReceiverTracker(ssc)
  inputInfoTracker = new InputInfoTracker(ssc)
  receiverTracker.start()
  jobGenerator.start()
  logInfo("Started JobScheduler")
}

14.在上段代碼中,首先會構造一個EventLoop[JobSchedulerEvent]對象,並調用其start方法

eventLoop.start()

15.讓JobScheduler的StreamingListenerBus對象監聽輸入流的ReceiverRateController對象

  for {
    inputDStream <- ssc.graph.getInputStreams
    rateController <- inputDStream.rateController
  } ssc.addStreamingListener(rateController)

StreamingContext.scala的第536行

def addStreamingListener(streamingListener: StreamingListener) {
  scheduler.listenerBus.addListener(streamingListener)
}

16.調用StreamingListenerBus的start方法

listenerBus.start(ssc.sparkContext)

17.實例化receiverTracker和InputInfoTracker,並調用receiverTracker的start方法

receiverTracker = new ReceiverTracker(ssc)
inputInfoTracker = new InputInfoTracker(ssc)
receiverTracker.start()

18.在receiverTracker的start方法中,會構造一個ReceiverTrackerEndpoint對象(ReceiverTracker.scala的第149行)

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

19.獲取各個InputDStream的receiver,並且在相應的worker節點啓動Receiver 。ReceiverTracker.scala的第413行

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

20.ReceiverTrackerEndpoint接收到StartAllReceivers消息,並做如下處理

ReceiverTracker.scala的第449行

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

在Executor上啓動receiver,此處可以得知,receiver可以有多個

21.然後回到13步的代碼,調用JobGenerator.start()

JobGenerator.scala的第78行

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


22.構造EventLoop[JobGeneratorEvent],並調用其start方法

  eventLoop.start()

23.判斷當前程序啓動時,是否使用Checkpoint數據做恢復,來選擇調用restart或者startFirstTime方法。我們的代碼將調用startFirstTime() 

JobGenerator.scala的第190行

private def startFirstTime() {
  val startTime = new Time(timer.getStartTime())
  graph.start(startTime - graph.batchDuration)
  timer.start(startTime.milliseconds)
  logInfo("Started JobGenerator at " + startTime)
}


24.調用DStreamGraph的start方法

def start(time: Time) {
  this.synchronized {
    require(zeroTime == null, "DStream graph computation already started")
    zeroTime = time
    startTime = time
    outputStreams.foreach(_.initialize(zeroTime))
    outputStreams.foreach(_.remember(rememberDuration))
    outputStreams.foreach(_.validateAtStart)
    inputStreams.par.foreach(_.start())
  }
}

此時,InputDStream啓動,並開始接收數據。

InputDStream和ReceiverInputDStream的start方法都是空的。

InputDStream.scala的第110行

/** Method called to start receiving data. Subclasses must implement this method. */
def start()

ReceiverInputDStream.scala的第63行

// Nothing to start or stop as both taken care of by the ReceiverTracker.
def start() {}

而SocketInputDStream沒有定義start方法,所以

inputStreams.par.foreach(_.start())

並沒有做任何的事情,那麼輸入流到底是怎麼被觸發並開始接收數據的呢?


我們再看上面的第20步:

startReceiver(receiver, executors)

代碼的具體實現在ReceiverTracker.scala的545行

private def startReceiver(
    receiver: Receiver[_],
    scheduledLocations: Seq[TaskLocation]): Unit = {
  def shouldStartReceiver: Boolean = {
    // It's okay to start when trackerState is Initialized or Started
    !(isTrackerStopping || isTrackerStopped)
  }

  val receiverId = receiver.streamId
  if (!shouldStartReceiver) {
    onReceiverJobFinish(receiverId)
    return
  }

  val checkpointDirOption = Option(ssc.checkpointDir)
  val serializableHadoopConf =
    new SerializableConfiguration(ssc.sparkContext.hadoopConfiguration)

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

它會將Receiver封裝成RDD,以Job的方式提交到Spark集羣中。submitJob的第二個參數,是一個函數,它的功能是在worker節點上啓動receiver

val supervisor = new ReceiverSupervisorImpl(
  receiver, SparkEnv.get, serializableHadoopConf.value, checkpointDirOption)
supervisor.start()
supervisor.awaitTermination()

在supervisor.start方法中會調用如下代碼

ReceiverSupervisor.scala的127行

/** Start the supervisor */
def start() {
  onStart()
  startReceiver()
}

onStart()方法是在ReceiverSupervisorImpl中實現的(ReceiverSupervisorImpl.scala的172行)

override protected def onStart() {
  registeredBlockGenerators.foreach { _.start() }
}


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)
    }
  } catch {
    case NonFatal(t) =>
      stop("Error starting receiver " + streamId, Some(t))
  }
}

首先會調用onReceiverStart方法


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

向ReceiverTrackerEndpoint發送消息,註冊Receiver給ReceiverTracker。


在startReceiver中,會調用receiver的Onstart方法,啓動receiver。


注:這裏要弄清楚ReceiverInputDStream和Recevier的區別。Receiver是具體接收數據的,而ReceiverInputDStream是對Receiver做了一成封裝,將數據轉換成DStream 。

我們本例中的Receiver是通過SocketInputDStream的getReceiver方法獲取的(在第19步的時候被調用)。

ReceiverInputDStream.scala的42行

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

而SocketReceiver會不斷的從Socket中獲取數據。

我們看看SocketReceiver的onStart方法:

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 {
    case e: java.net.ConnectException =>
      restart("Error connecting to " + host + ":" + port, e)
    case NonFatal(e) =>
      logWarning("Error receiving data", e)
      restart("Error receiving data", e)
  } finally {
    if (socket != null) {
      socket.close()
      logInfo("Closed socket to " + host + ":" + port)
    }
  }
}

到目前爲止,我們的Receiver啓動並接收數據啦。Receiver的啓動是以Spark Job的方式啓動的。


25. SparkStreaming是如何每個batchInterval時間提交Job的?

我們回到第22步,在JobGenerator啓動的過程中創建了一個EventLoop[JobGeneratorEvent],並調用了start方法。代碼如下:

def start(): Unit = {
  if (stopped.get) {
    throw new IllegalStateException(name + " has already been stopped")
  }
  // Call onStart before starting the event thread to make sure it happens before onReceive
  onStart()
  eventThread.start()
}

它啓動了一個eventThread線程,在其run方法中調用了EventLoop的onReceive方法

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

  override def run(): Unit = {
    try {
      while (!stopped.get) {
        val event = eventQueue.take()
        try {
          onReceive(event)
        } catch {
          case NonFatal(e) => {
            try {
              onError(e)
            } catch {
              case NonFatal(e) => logError("Unexpected error in " + name, e)
            }
          }
        }
      }
    } catch {
      case ie: InterruptedException => // exit even if eventQueue is not empty
      case NonFatal(e) => logError("Unexpected error in " + name, e)
    }
  }

}

而EventLoop的OnReceive方法如下:

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

調用processEvent方法:

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

在這裏我們看到了,按照給定的時間去生成Jobs 

case GenerateJobs(time) => generateJobs(time)

繼續跟蹤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
    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))
}

然後調用graph.generateJobs:

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
}

在調用輸出流的generateJob

private[streaming] def generateJob(time: Time): Option[Job] = {
  getOrCompute(time) match {
    case Some(rdd) => {
      val jobFunc = () => {
        val emptyFunc = { (iterator: Iterator[T]) => {} }
        context.sparkContext.runJob(rdd, emptyFunc)
      }
      Some(new Job(time, jobFunc))
    }
    case None => None
  }
}

這裏先getOrCompute方法,生成指定時間的RDD:

/**
 * Get the RDD corresponding to the given time; either retrieve it from cache
 * or compute-and-cache it.
 */
private[streaming] final def getOrCompute(time: Time): Option[RDD[T]] = {
  // If RDD was already generated, then retrieve it from HashMap,
  // or else compute the RDD
  generatedRDDs.get(time).orElse {
    // Compute the RDD if time is valid (e.g. correct time in a sliding window)
    // of RDD generation, else generate nothing.
    if (isTimeValid(time)) {

      val rddOption = createRDDWithLocalProperties(time, displayInnerRDDOps = false) {
        // 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. We need to have this call here because
        // compute() might cause Spark jobs to be launched.
        PairRDDFunctions.disableOutputSpecValidation.withValue(true) {
          compute(time)
        }
      }

      rddOption.foreach { case newRDD =>
        // Register the generated RDD for caching and checkpointing
        if (storageLevel != StorageLevel.NONE) {
          newRDD.persist(storageLevel)
          logDebug(s"Persisting RDD ${newRDD.id} for time $time to $storageLevel")
        }
        if (checkpointDuration != null && (time - zeroTime).isMultipleOf(checkpointDuration)) {
          newRDD.checkpoint()
          logInfo(s"Marking RDD ${newRDD.id} for time $time for checkpointing")
        }
        generatedRDDs.put(time, newRDD)
      }
      rddOption
    } else {
      None
    }
  }
}

終於,將job通過SparkContext的runJob方法提交給了Spark集羣。

所謂一圖勝千言,下面是Streaming Job運行的流程圖:

wKiom1cuzmvBXlpGAAI_pUai9Xk000.png

備註:

1、DT大數據夢工廠微信公衆號DT_Spark 
2、IMF晚8點大數據實戰YY直播頻道號:68917580
3、新浪微博: http://www.weibo.com/ilovepains


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章