第9课:Spark Streaming源码解读之Receiver在Driver的精妙实现全生命周期彻底研究和思考

来源:互联网 发布:linux连接mysql数据库 编辑:程序博客网 时间:2024/06/05 01:00

Spark Streaming为什么需要receiver?因为Spark Streaming需要通过Receiver持续不断的从外部数据源接收数据,并把数据汇报给Driver端,由此才能在每个Batch Durations根据汇报的数据生成不同的Job,才有后面的故事。
Receiver属于Spark Streaming应用程序启动的一部分,而不是Spark Streaming用程序具体执行的一部分,Receiver是随着应用程序启动的时候启动的。
做实验常用1个InputDStream,对应1个Receiver,但事实上可以有多个InputDStream,对应就有多个Receiver.可以认为Receivers和InputDStreams是一一对应的。

借助sparkcore上的job的方式来启动receiver,可能会有以下问题:
多个InputDStream对应多个Receivers对应多个数据分片partitions。(OutputDStream也可以有多个)
启动Receivers的时候,在多个机器上启动Receivers,理想的情况下是在不同的机器上启动Receiver,但是从Spark Core的角度来看启动Receivers就是普通的应用程序,Spark Core感觉不到Receiver的特殊性,所以就会按照正常的Job启动的方式来处理,极有可能在一个Executor上启动多个Receiver.这样的话就可能导致负载不均衡;
到executor上启动Receiver有可能因executor的各种情况导致task失败,也就是job失败,(Receiver以启动job的task的方式来启动)则Receiver启动失败,而我们只要集群存在,就希望Receiver一定启动成功;
运行过程中,启动Receiver的task有可能失败,例如executor挂掉,则以task为单位启动的receiver也会挂掉;上述从启动和运行两个层面来看的的问题,都会导致数据接收失败。
综合起来看,借助sparkcore上的job的方式来启动receiver很巧妙,但task可能失败,虽然有task retry,但也会导致task失败,进而导致Job失败,进而导致接受不到数据,进而会导致应用程序失败。

那streaming框架到底是怎么启动Receiver的呢?
StreamingContext.socketTextStream没有,DStream的transformation操作也没有(transformation已经在使用数据了),启动Receiver是在StreamingContext.start()中。
使用子线程2个原因:新线程本地参数的修改不会影响原有的主线程;可以避免阻塞主线程。
receiverTracker.start():
ReceiverTracker的start方法启动RPC消息通信体,为啥呢?因为receiverTracker会监控整个集群中的Receiver,Receiver转过来要向ReceiverTrackerEndpoint汇报自己的状态,包括接收的数据,生命周期等信息。
Receiver的启动是基于输入数据流的,若没有InputDStream就不会启动Receiver.
launchReceivers()是关键!基于ReceiverInputDStream(在driver端,是元对象metaObject,代表了输入流)来得到逻辑级别的Receivers,再分发到worker节点上去物理级别地执行。(MASTER-SLAVE结构)
一个InputDStream只产生一个Receiver。通过getReceiver()来产生Receiver。

Receivers封装完内部的Receiver后runDummySparkJob(),然后发出消息,消息是个case class. endpoint.send(StartAllReceivers(receivers))
rpc通信对象(在start时构建)接受到消息StartAllReceivers(receivers)后:
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)
}

调度模式确定receivers可以运行在那些Executors上。
根据receiver的streamID找到它可以运行在哪些Executors上.
接下来惊心动魄的startReceiver(receiver, executors)
不是基于sparkCore的task的分布方式,而是Streaming自己决定receiver在哪台机器上执行,driver层面强制指定了执行的location.
TaskLocation.scala: 不是executor而是host级别的。
startReceiver()精彩!
终止启动receiver,意味着你不需要重启spark job.
如果想重启一个receiver,会重新调度而不是重试(task失败会重试,启动receiver不会重试)。
启动receiver监控器supervisor,统筹数据的写操作。
在它的start方法中,真正启动了receiver.

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

val future = ssc.sparkContext.submitJob[Receiver[_], Unit, Unit](
为了启动receiver,启动了一个spark作业来运行receiver.这里是运行一个还是几个receiver?
一个应用程序可以有多个receiver。
每一个receiver的启动都会触发一个作业,而不是一个作业启动所有的receiver。
基于1个Job的多个task来启动多个receiver的缺点:任务倾斜问题,task启动失败和task运行失败导致。每次都是启动一个Job保证了最大程度的负载均衡。但也可能失败,此时不会认为作业失败,而是若无其事地正常发消息:self.send(RestartReceiver(receiver)),然后再次重试再次调度,绕开了task重试次数的限制,确保集群中一定会启动receiver。
调度后每个receiver有一系列可选的机器列表,这里从列表里减去失败的机器,若减为空了,则再次调用.
负载均衡最好的方式:
每个Job只有一个数据,只有一个task,只在一个executor上运行,最大程度地保证了负载均衡,
这比把所有receiver放到一个job中
receiver。

缓冲线程池中运行,不断地循环,并发地启动receiver,(receiver对应的数据源不同,没有关系,可以并行启动)。

schedulingPolicy.

本次分享来自于王家林老师的课程‘源码版本定制发行班’,在此向王家林老师表示感谢!
欢迎大家交流技术知识!一起学习,共同进步!

0 0
原创粉丝点击