Spark Streaming源碼解讀之Receiver生成全生命周期徹底研究和思考

一:Receiver啟動(dòng)的方式設(shè)想

1.Spark Streaming通過Receiver持續(xù)不斷的從外部數(shù)據(jù)源接收數(shù)據(jù),并把數(shù)據(jù)匯報(bào)給Driver端,由此每個(gè)Batch Durations就可以根據(jù)匯報(bào)的數(shù)據(jù)生成不同的Job,在不同的機(jī)器之上啟動(dòng),每個(gè)reveiver 相當(dāng)于一個(gè)分片,由于Sapark core 感覺不到它的特殊性,按普通的調(diào)度,即有可能在同一個(gè)Executor之中啟動(dòng)多個(gè)Receiver,這種情況之下導(dǎo)致負(fù)載不均勻或者由于Executor運(yùn)行本身的故障,task 有可能啟動(dòng)失敗,整個(gè)job啟動(dòng)就失敗,即receiver啟動(dòng)失敗。

啟動(dòng)Receiver

1. 從Spark Core的角度來看,Receiver的啟動(dòng)Spark Core并不知道, Receiver是通過Job的方式啟動(dòng)的,運(yùn)行在Executor之上的,由task運(yùn)行。

2. 一般情況下,只有一個(gè)Receiver,但是可以創(chuàng)建不同的數(shù)據(jù)來源的InputDStream.

3.啟動(dòng)Receiver的時(shí)候,實(shí)其上一個(gè)receiver就是一個(gè)partition分片,由一個(gè)Job啟動(dòng),這個(gè)Job里面有RDD的transformations操作和action的操作,隨著定時(shí)器觸發(fā),不斷的產(chǎn)生有數(shù)據(jù)接收,每個(gè)時(shí)間段中產(chǎn)生的接收數(shù)據(jù)實(shí)其上就是一個(gè)partition分片,

4.? 以上設(shè)計(jì)思想產(chǎn)生的如下問題:

(1)如果有多個(gè)InputDStream,那就要啟動(dòng)多個(gè)Receiver,每個(gè)Receiver也就相當(dāng)于分片partition,那我啟動(dòng)Receiver的時(shí)候理想的情況下是在不同的機(jī)器上啟動(dòng)Receiver,但是SparkCore的角度來看就是應(yīng)用程序,感覺不到Receiver的特殊性,所以就會(huì)按照正常的Job啟動(dòng)的方式來處理,極有可能在一個(gè)Executor上啟動(dòng)多個(gè)Receiver.這樣的話就可能導(dǎo)致負(fù)載不均衡。(2)有可能啟動(dòng)Receiver失敗,只要集群存在,Receiver就不應(yīng)該啟動(dòng)失敗。

(3)從運(yùn)行過程中看,一個(gè)Reveiver就是一個(gè)partition的話,啟動(dòng)的由一個(gè)Task,如果Task啟動(dòng)失敗,相應(yīng)的Receiver也會(huì)失敗。由此,可以得出,對(duì)于Receiver失敗的話,后果是非常嚴(yán)重的,那么在SparkStreaming如何防止這些事的呢?Spark Streaming源碼分析,在Spark Streaming之中就指定如下信息:

一是Spark使用一個(gè)Job啟動(dòng)一個(gè)Receiver.最大程度的保證了負(fù)載均衡。

二是Spark Streaming已經(jīng)指定每個(gè)Receiver運(yùn)行在那些Executor上,在Receiver運(yùn)行之前就指定了運(yùn)行的地方!

三是 如果Receiver啟動(dòng)失敗,此時(shí)并不是Job失敗,在內(nèi)部會(huì)重新啟動(dòng)Receiver.

在StreamingContext的start方法被調(diào)用的時(shí)候,JobScheduler的start

def start(): Unit = synchronized {

state match {

caseINITIALIZED =>

startSite.set(DStream.getCreationSite())

StreamingContext.ACTIVATION_LOCK.synchronized {

StreamingContext.assertNoOtherContextIsActive()

try {

validate()

// Startthe streaming scheduler in a new thread, so that

thread local properties

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

//啟動(dòng)子線程,一方面為了本地初始化工作,另外一方面是不要阻塞主線程。

scheduler.start()

}

state =StreamingContextState.ACTIVE

} catch {

caseNonFatal(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 newIllegalStateException("StreamingContext has already

been stopped")

}

}

2.而在JobScheduler的start方法中ReceiverTracker的start方法被調(diào)用,Receiver就啟動(dòng)了。

def start(): Unit = synchronized {

if (eventLoop !=null) return // scheduler has already been

started

logDebug("Starting JobScheduler")

eventLoop = newEventLoop[JobSchedulerEvent]("JobScheduler")

{

overrideprotected def onReceive(event: JobSchedulerEvent):

Unit = processEvent(event)

overrideprotected def onError(e: Throwable): Unit =

reportError("Error in jobscheduler", e)

}

eventLoop.start()

// attach ratecontrollers 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)

//啟動(dòng)receiverTracker

receiverTracker.start()

jobGenerator.start()

logInfo("Started JobScheduler")

}

3.ReceiverTracker的start方法啟動(dòng)RPC消息通信體,為啥呢?因?yàn)閞eceiverTracker會(huì)監(jiān)控整個(gè)集群中的Receiver,Receiver轉(zhuǎn)過來要向ReceiverTrackerEndpoint匯報(bào)自己的狀態(tài),接收的數(shù)據(jù),包括生命周期等信息

def start(): Unit = synchronized {

if(isTrackerStarted) {

throw newSparkException("ReceiverTracker already started")

}

//Receiver的啟動(dòng)是依據(jù)輸入數(shù)據(jù)流的。

if(!receiverInputStreams.isEmpty) {

endpoint =ssc.env.rpcEnv.setupEndpoint(

"ReceiverTracker",

newReceiverTrackerEndpoint(ssc.env.rpcEnv))

if(!skipReceiverLaunch) launchReceivers()

logInfo("ReceiverTracker started")

trackerState =Started

}

}

4.基于ReceiverInputDStream(是在Driver端)來獲得具體的Receivers實(shí)例,然后再把他們分不到Worker節(jié)點(diǎn)上。一個(gè)ReceiverInputDStream只產(chǎn)生一個(gè)Receiver

private def launchReceivers(): Unit = {

val receivers =receiverInputStreams.map(nis => {

//一個(gè)數(shù)據(jù)輸入來源(receiverInputDStream)只產(chǎn)生一個(gè)Receiver

val rcvr =nis.getReceiver()

rcvr.setReceiverId(nis.id)

rcvr

})

runDummySparkJob()

logInfo("Starting " + receivers.length + "receivers")

//此時(shí)的endpoint就是上面代碼中在ReceiverTracker的start方法中構(gòu)造的ReceiverTrackerEndpoint

endpoint.send(StartAllReceivers(receivers))

}

5. 其中runDummySparkJob()為了確保所有節(jié)點(diǎn)活著,而且避免所有的receivers集中在一個(gè)節(jié)點(diǎn)上。

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)

}

ReceiverInputDStream中的getReceiver()方法獲得receiver對(duì)象然后將它發(fā)送到worker節(jié)點(diǎn)上實(shí)例化receiver,然后去接收數(shù)據(jù)。

def getReceiver(): Receiver[T] //返回的是Receiver對(duì)象

6. ?根據(jù)繼承關(guān)系,這里看一下SocketInputDStream中的getReceiver方法。

def getReceiver(): Receiver[T] = {

newSocketReceiver(host, port, bytesToObjects,

storageLevel)

}

}

啟動(dòng)后臺(tái)線程,調(diào)用receive方法。

private[streaming]

class SocketReceiver[T: ClassTag](

host: String,

port: Int,

bytesToObjects:InputStream => Iterator[T],

storageLevel:StorageLevel

) extendsReceiver[T](storageLevel) with Logging {

def onStart() {

// Start thethread that receives data over a connection

newThread("Socket Receiver") {

setDaemon(true)

override defrun() { receive() }

}.start()

}

啟動(dòng)socket開始接收數(shù)據(jù)。

/** Create a socket connection and receive data untilreceiver is

stopped */

def receive() {

var socket:Socket = null

try {

logInfo("Connecting to " + host + ":" + port)

socket = newSocket(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)

caseNonFatal(e) =>

logWarning("Error receiving data", e)

restart("Error receiving data", e)

} finally {

if (socket !=null) {

socket.close()

logInfo("Closed socket to " + host + ":" + port)

}

}

}

}

7.?ReceiverTrackerEndpoint源碼如下:

/** RpcEndpoint to receive messages from the receivers.*/

private class ReceiverTrackerEndpoint(override valrpcEnv: RpcEnv)

extends ThreadSafeRpcEndpoint {

// TODO Removethis thread pool after

https://github.com/apache/spark/issues/7385 is merged

private valsubmitJobThreadPool =

ExecutionContext.fromExecutorService(

ThreadUtils.newDaemonCachedThreadPool("submit-job-thread-pool"))

private valwalBatchingThreadPool =

ExecutionContext.fromExecutorService(

ThreadUtils.newDaemonCachedThreadPool("wal-batching-thread-pool"))

@volatile privatevar active: Boolean = true

override defreceive: PartialFunction[Any, Unit] = {

// Localmessages

caseStartAllReceivers(receivers) =>

valscheduledLocations =

// schedulingPolicy調(diào)度策略

//receivers就是要啟動(dòng)的receiver

//getExecutors獲得集群中的Executors的列表

// scheduleReceivers就可以確定receiver可以運(yùn)行在哪些Executor上

schedulingPolicy.scheduleReceivers(receivers,getExecutors)

for (receiver<- receivers) {

//

scheduledLocations根據(jù)receiver的Id就找到了當(dāng)前那些Executors可以運(yùn)行Receiver

val executors= scheduledLocations(receiver.streamId)

updateReceiverScheduledExecutors(receiver.streamId,

executors)

receiverPreferredLocations(receiver.streamId)

=receiver.preferredLocation

//上述代碼之后要啟動(dòng)的Receiver確定了,具體Receiver運(yùn)行在哪些Executors上也確定了。

//循環(huán)receivers,每次將一個(gè)receiver傳入過去。

startReceiver(receiver, executors)

}

//用于接收RestartReceiver消息,從新啟動(dòng)Receiver.

caseRestartReceiver(receiver) =>

// Oldscheduled executors minus the ones that are not active

any more

//如果Receiver失敗的話,從可選列表中減去。

valoldScheduledExecutors =

//剛在調(diào)度為Receiver分配給哪個(gè)Executor的時(shí)候會(huì)有一些列可選的Executor列表

getStoredScheduledExecutors(receiver.streamId)

//從新獲取Executors

valscheduledLocations = if (oldScheduledExecutors.nonEmpty)

{

// Tryglobal scheduling again

oldScheduledExecutors

} else {

//如果可選的Executor使用完了,則會(huì)重新執(zhí)行rescheduleReceiver重新獲取Executor.

valoldReceiverInfo =

receiverTrackingInfos(receiver.streamId)

// Clear"scheduledLocations" to indicate we are going to

do local scheduling

valnewReceiverInfo = oldReceiverInfo.copy(

state =ReceiverState.INACTIVE, scheduledLocations =

None)

receiverTrackingInfos(receiver.streamId) =

newReceiverInfo

schedulingPolicy.rescheduleReceiver(

receiver.streamId,

receiver.preferredLocation,

receiverTrackingInfos,

getExecutors)

}

// Assumethere is one receiver restarting at one time, so we

don't need to update

//receiverTrackingInfos

//重復(fù)調(diào)用startReceiver

startReceiver(receiver, scheduledLocations)

case c:CleanupOldBlocks =>

receiverTrackingInfos.values.flatMap(_.endpoint).foreach(_.send(c))

caseUpdateReceiverRateLimit(streamUID, newRate) =>

for (info<- receiverTrackingInfos.get(streamUID); eP

<- info.endpoint) {

eP.send(UpdateRateLimit(newRate))

}

// Remotemessages

caseReportError(streamId, message, error) =>

reportError(streamId, message, error)

}

8.? 從注釋中可以看到,Spark Streaming指定receiver在那些Executors運(yùn)行,而不是基于Spark

Core中的Task來指定。

Spark使用submitJob的方式啟動(dòng)Receiver,而在應(yīng)用程序執(zhí)行的時(shí)候會(huì)有很多Receiver,這個(gè)時(shí)候是啟動(dòng)一個(gè)Receiver呢,還是把所有的Receiver通過這一個(gè)Job啟動(dòng)?

在ReceiverTracker的receive方法中startReceiver方法第一個(gè)參數(shù)就是receiver,從實(shí)現(xiàn)的可以看出for循環(huán)不 斷取出receiver,然后調(diào)用startReceiver。由此就可以得出一個(gè)Job只啟動(dòng)一個(gè)Receiver.

如果Receiver啟動(dòng)失敗,此時(shí)并不會(huì)認(rèn)為是作業(yè)失敗,會(huì)重新發(fā)消息給ReceiverTrackerEndpoint重新啟動(dòng)Receiver,這樣也就確保了Receivers一定會(huì)被啟動(dòng),這樣就不會(huì)像Task啟動(dòng)Receiver的話如果失敗受重試次數(shù)的影響。

private def startReceiver(

receiver:Receiver[_],

// scheduledLocations指定的是在具體的那臺(tái)物理機(jī)器上執(zhí)行。

scheduledLocations: Seq[TaskLocation]): Unit = {

//判斷下Receiver的狀態(tài)是否正常。

defshouldStartReceiver: Boolean = {

// It's okay tostart when trackerState is Initialized or

Started

!(isTrackerStopping || isTrackerStopped)

}

val receiverId =receiver.streamId

//如果不需要啟動(dòng)Receiver則會(huì)調(diào)用onReceiverJobFinish()

if(!shouldStartReceiver) {

onReceiverJobFinish(receiverId)

return

}

valcheckpointDirOption = Option(ssc.checkpointDir)

valserializableHadoopConf =

newSerializableConfiguration(ssc.sparkContext.hadoopConfiguration)

//startReceiverFunc封裝了在worker上啟動(dòng)receiver的動(dòng)作。

// Function tostart the receiver on the worker node

valstartReceiverFunc: Iterator[Receiver[_]] => Unit =

(iterator:Iterator[Receiver[_]]) => {

if(!iterator.hasNext) {

throw newSparkException(

"Could not start receiver as object not found.")

}

if(TaskContext.get().attemptNumber() == 0) {

valreceiver = iterator.next()

assert(iterator.hasNext == false)

// ReceiverSupervisorImpl是Receiver的監(jiān)控器,同時(shí)負(fù)責(zé)數(shù)據(jù)的寫等操作。

valsupervisor = new ReceiverSupervisorImpl(

receiver,SparkEnv.get, serializableHadoopConf.value,

checkpointDirOption)

supervisor.start()

supervisor.awaitTermination()

} else {

//如果你想重新啟動(dòng)receiver的話,你需要重新完成上面的調(diào)度,從新schedule,而不是Task重試。

// It'srestarted by TaskScheduler, but we want to

reschedule it again. So exit it.

}

}

// Create the RDDusing the scheduledLocations to run the

receiver in a Spark job

val receiverRDD:RDD[Receiver[_]] =

if(scheduledLocations.isEmpty) {

ssc.sc.makeRDD(Seq(receiver), 1)

} else {

valpreferredLocations =

scheduledLocations.map(_.toString).distinct

ssc.sc.makeRDD(Seq(receiver -> preferredLocations))

}

//receiverId可以看出,receiver只有一個(gè)

receiverRDD.setName(s"Receiver $receiverId")

ssc.sparkContext.setJobDescription(s"Streaming job running

receiver$receiverId")

ssc.sparkContext.setCallSite(Option(ssc.getStartSite()).getOrElse(Utils.getCallSite()))

//每個(gè)Receiver的啟動(dòng)都會(huì)觸發(fā)一個(gè)Job,而不是一個(gè)作業(yè)的Task去啟動(dòng)所有的Receiver.

//應(yīng)用程序一般會(huì)有很多Receiver,

//調(diào)用SparkContext的submitJob,為了啟動(dòng)Receiver,啟動(dòng)了Spark一個(gè)作業(yè).

val future =ssc.sparkContext.submitJob[Receiver[_], Unit,

Unit](

receiverRDD,startReceiverFunc, Seq(0), (_, _) => Unit,

())

// We will keeprestarting the receiver job until ReceiverTracker

is stopped

future.onComplete{

case Success(_)=>

// shouldStartReceiver默認(rèn)是true

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

//RestartReceiver

self.send(RestartReceiver(receiver))

}

//使用線程池的方式提交Job,這樣的好處是可以并發(fā)的啟動(dòng)Receiver。

}(submitJobThreadPool)

logInfo(s"Receiver ${receiver.streamId} started")

}

9. 當(dāng)Receiver啟動(dòng)失敗的話,就會(huì)調(diào)用ReceiverTrackEndpoint重新啟動(dòng)一個(gè)Spark

Job去啟動(dòng)Receiver.

/**

* This messagewill trigger ReceiverTrackerEndpoint to restart a

Spark job for the receiver.

*/

private[streaming] case class

RestartReceiver(receiver:Receiver[_])

extendsReceiverTrackerLocalMessage

11. 當(dāng)Receiver關(guān)閉的話,并不需要重新啟動(dòng)Spark Job.

/**

* Call when areceiver is terminated. It means we won't restart

its Spark job.

*/

private def onReceiverJobFinish(receiverId: Int): Unit ={

receiverJobExitLatch.countDown()

//使用foreach將receiver從receiverTrackingInfo中去掉。

receiverTrackingInfos.remove(receiverId).foreach {

receiverTrackingInfo=>

if(receiverTrackingInfo.state == ReceiverState.ACTIVE) {

logWarning(s"Receiver $receiverId exited but didn't

deregister")

}

}

}

12.

Supervisor.start(),在子類ReceiverSupervisorImpl中并沒有start方法,因此調(diào)用的是父類ReceiverSupervisor的start方法。

/** Start the supervisor */

def start() {

onStart() //具體實(shí)現(xiàn)是子類實(shí)現(xiàn)的。

startReceiver()

}

Onstart方法源碼如下:

/**

* Called whensupervisor is started.

* Note that thismust be called before the receiver.onStart() is

called to ensure

* things like[[BlockGenerator]]s are started before the receiver

starts sending data.

*/

protected def onStart() { }

其具體實(shí)現(xiàn)是在子類的ReceiverSupervivorImpl的onstart方法

override protected def onStart() {

registeredBlockGenerators.foreach { _.start() }

}

此時(shí)的start方法調(diào)用的是BlockGenerator的start方法。

/** Start block generating and pushing threads. */

def start(): Unit = synchronized {

if (state ==Initialized) {

state = Active

blockIntervalTimer.start()

blockPushingThread.start()

logInfo("Started BlockGenerator")

} else {

throw newSparkException(

s"Cannotstart BlockGenerator as its not in the Initialized

state [state =$state]")

}

}

備注:

資料來源于:DT_大數(shù)據(jù)夢(mèng)工廠(Spark發(fā)行版本定制)

更多私密內(nèi)容,請(qǐng)關(guān)注微信公眾號(hào):DT_Spark

如果您對(duì)大數(shù)據(jù)Spark感興趣,可以免費(fèi)聽由王家林老師每天晚上20:00開設(shè)的Spark永久免費(fèi)公開課,地址YY房間號(hào):68917580

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容