Spark中LiveListenerBus的解析
要理解LiveListenerBus,可以先看下面几个问题
如果我们要监听Spark执行任务过程中的Job、stage、task 等事件的开始结束,我们该如何做
我们只要实现一个实现SparkListenerInterface 接口的监听器Listerner
1 private[spark] trait SparkListenerInterface { 2 3 def onStageCompleted(stageCompleted: SparkListenerStageCompleted): Unit 4 5 def onStageSubmitted(stageSubmitted: SparkListenerStageSubmitted): Unit 6 7 def onTaskStart(taskStart: SparkListenerTaskStart): Unit 8 9 ....等许多监听的方法,这个方法当事件一发生,就是被触发 10 }
然后通过SparkContext.addSparkListener(listener) 方法,就能把监听器注册到spark中,通过对应的方法,得到事件通知
那么LiveListenerBus究竟是什么?做什么用的?
刚才上面我们做的Spark的监听器,是由谁来通知呢?是由谁来接收事件?就是由LiveListenerBus来实现的,简单的说,LiveListenerBus负责Spark中事件的接收 和 通知
专业说法:LiveListenerBus继承了SparkListenerBus,并实现了将事件异步投递给监听器,达到实时刷新UI界面数据的效果。
那么LiveListenerBus是如何实现的?那就跟着源码一起来研究
入口:在SparkContext类的初始化中,有这么一句代码,这句就是 LiveListenerBus 的初始化
1 _listenerBus = new LiveListenerBus(_conf)
进入 LiveListenerBus 查看,我们在这里,把 LiveListenerBus 类用简单代码的方式展示(即不是源代码,但是意思很清晰),方便大家理解
1 private[spark] class LiveListenerBus(conf: SparkConf) { 2 private val started = new AtomicBoolean(false) 3 4 //保存Listerner监听器的队列 5 private val queues = new CopyOnWriteArrayList[AsyncEventQueue]() 6 7 //在start() 方法还未启动之前,事件先存入到这个队列中,如果存满会丢弃,然后打印warn日志 8 @volatile private[scheduler] var queuedEvents = 9 new mutable.ListBuffer[SparkListenerEvent]() 10 11 12 //Spark 在要触发事件时,会把相应的事件放入到 LiveListenerBus 中 13 def post(event: SparkListenerEvent): Unit = { 14 // 在start方法启动之后,就直接通知每个Listener 15 // queuedEvents 为null代表start方法已经启动执行完 16 if (queuedEvents == null) { 17 postToQueues(event) 18 return 19 } 20 21 //判断是否启动start方法,如果没启动,就把event放入到 queuedEvents 中 22 //如果启动了,就跳过这步直接到下一步 23 synchronized { 24 if (!started.get()) { 25 queuedEvents += event 26 return 27 } 28 } 29 30 //直接通知所有Listener 31 val it = queues.iterator() 32 while (it.hasNext()) { 33 it.next().post(event) 34 } 35 } 36 37 // 增加监听器的入口,SparkContext.addListern() 最终也是调用这个方法 38 def addToEventLogQueue(listener: SparkListenerInterface): Unit = { 39 queues.asScala.find(_.name == EVENT_LOG_QUEUE) match { 40 case Some(queue) => queue.addListener(listener) 41 case None => val newQueue = new AsyncEventQueue(queue, conf, metrics, this) 42 newQueue.addListener(listener) 43 if (started.get()) { 44 newQueue.start(sparkContext) 45 } 46 queues.add(newQueue) 47 } 48 } 49 50 //在启动前,Spark所有的事件都会通过Post发送到 queuedEvents 中 51 //启动后,LiveListenerBus 就会给每个Listener发送每个Event 52 def start(sc: SparkContext, metricsSystem: MetricsSystem): Unit = synchronized { 53 54 this.sparkContext = sc 55 queues.asScala.foreach { q => 56 57 /** 58 * 启动 AsyncEventQueue q 事件队列 59 */ 60 q.start(sc) 61 62 //循环把事件添加到 AsyncEventQueue q 事件队列中 63 queuedEvents.foreach(q.post) 64 } 65 queuedEvents = null 66 67 //注册启动监控 68 metricsSystem.registerSource(metrics) 69 } 70 }
步骤:
1、增加Listener,通过addToEventLogQueue( listener ) 注册监听器到 LiveListenerBus 中
2、在start() 方法还未执行前,Spark在每个阶段时,都会将对应的事件,用 LiveListenerBus.post( event ) 方法添加到 queuedEvents 缓存队列中,
当执行 start() 方法后,再调用post( event )的时候,就直接把事件发给每个 Listener即可
3、启动start() 方法
就会 给所有的 Listener 通知 所有缓存中的 event 事件,然后 把 queuedEvents 至为null,以后在执行post( event ) 的时候,就可以直接通知所有Listener,就不用存入到 queuedEvents 缓存队列中
通知完所有Listener 所有的event后,注册启动监控
这里有两个问题
1、注册启动监控 的 metricsSystem.registerSource(metrics) 这句代码什么意思?
2、Listener 如何被通知到?
Listener 如何被通知到?
【Listener如何被通知到? stage1】
我们看代码
1 this.sparkContext = sc 2 queues.asScala.foreach { q => 3 4 /** 5 * 启动 AsyncEventQueue q 事件队列 6 */ 7 q.start(sc) 8 9 //循环把事件添加到 AsyncEventQueue q 事件队列中 10 queuedEvents.foreach(q.post) 11 }
【Listener如何被通知到? stage2】
q.start( sc ) 的 q 是AsyncEventQueue,我们查看start( event ) 方法内部做了什么
org.apache.spark.scheduler.AsyncEventQueue#start
1 private[scheduler] def start(sc: SparkContext): Unit = { 2 if (started.compareAndSet(false, true)) { 3 this.sc = sc 4 5 //【重点】启动 DispatcherThread 事件分发器 线程 6 dispatchThread.start() 7 } else { 8 throw new IllegalStateException(s"$name already started!") 9 } 10 }
【Listener如何被通知到? stage3】
org.apache.spark.scheduler.AsyncEventQueue#dispatchThread
1 private val dispatchThread = new Thread(s"spark-listener-group-$name") { 2 setDaemon(true) 3 4 override def run(): Unit = Utils.tryOrStopSparkContext(sc) { 5 6 /** 7 * 【重点】启动分发 8 */ 9 dispatch() 10 } 11 }
【Listener如何被通知到? stage4】
org.apache.spark.scheduler.AsyncEventQueue#dispatch
1 private def dispatch(): Unit = LiveListenerBus.withinListenerThread.withValue(true) { 2 3 // 取出队列中的第一个事件 4 var next: SparkListenerEvent = eventQueue.take() 5 while (next != POISON_PILL) { 6 val ctx = processingTime.time() 7 try { 8 9 /** 10 * 【重点】提交 11 */ 12 super.postToAll(next) 13 14 } finally { 15 ctx.stop() 16 } 17 eventCount.decrementAndGet() 18 19 //【重点】再获取下一个 20 next = eventQueue.take() 21 } 22 eventCount.decrementAndGet() 23 }
【Listener如何被通知到? stage5】
org.apache.spark.util.ListenerBus#postToAll
1 def postToAll(event: E): Unit = { 2 // JavaConverters can create a JIterableWrapper if we use asScala. 3 // However, this method will be called frequently. To avoid the wrapper cost, here we use 4 // Java Iterator directly. 5 val iter = listenersPlusTimers.iterator 6 while (iter.hasNext) { 7 val listenerAndMaybeTimer = iter.next() 8 val listener = listenerAndMaybeTimer._1 9 val maybeTimer = listenerAndMaybeTimer._2 10 val maybeTimerContext = if (maybeTimer.isDefined) { 11 maybeTimer.get.time() 12 } else { 13 null 14 } 15 try { 16 17 //【重点】提交 18 doPostEvent(listener, event) 19 20 if (Thread.interrupted()) { 21 // We want to throw the InterruptedException right away so we can associate the interrupt 22 // with this listener, as opposed to waiting for a queue.take() etc. to detect it. 23 throw new InterruptedException() 24 } 25 } catch { 26 case ie: InterruptedException => logError( 27 s"Interrupted while posting to ${Utils.getFormattedClassName(listener)}. " + s"Removing that listener.", ie) 28 removeListenerOnError(listener) 29 case NonFatal(e) if !isIgnorableException(e) => logError(s"Listener ${Utils.getFormattedClassName(listener)} threw an exception", e) 30 } finally { 31 if (maybeTimerContext != null) { 32 maybeTimerContext.stop() 33 } 34 } 35 } 36 }
【Listener如何被通知到? stage6】
org.apache.spark.scheduler.SparkListenerBus#doPostEvent
在这里,就是去通知Lisnter的对应事件的对应方法
1 /** 2 * 提交:SparkListenerApplicationStart 事件 3 */ 4 protected override def doPostEvent(listener: SparkListenerInterface, event: SparkListenerEvent): Unit = { 5 event match { 6 case stageSubmitted: SparkListenerStageSubmitted => listener.onStageSubmitted(stageSubmitted) 7 case stageCompleted: SparkListenerStageCompleted => listener.onStageCompleted(stageCompleted) 8 case jobStart: SparkListenerJobStart => listener.onJobStart(jobStart) 9 case jobEnd: SparkListenerJobEnd => listener.onJobEnd(jobEnd) 10 case taskStart: SparkListenerTaskStart => listener.onTaskStart(taskStart) 11 case taskGettingResult: SparkListenerTaskGettingResult => listener.onTaskGettingResult(taskGettingResult) 12 case taskEnd: SparkListenerTaskEnd => listener.onTaskEnd(taskEnd) 13 case environmentUpdate: SparkListenerEnvironmentUpdate => listener.onEnvironmentUpdate(environmentUpdate) 14 case blockManagerAdded: SparkListenerBlockManagerAdded => listener.onBlockManagerAdded(blockManagerAdded) 15 case blockManagerRemoved: SparkListenerBlockManagerRemoved => listener.onBlockManagerRemoved(blockManagerRemoved) 16 case unpersistRDD: SparkListenerUnpersistRDD => listener.onUnpersistRDD(unpersistRDD) 17 case applicationStart: SparkListenerApplicationStart => listener.onApplicationStart(applicationStart) 18 case applicationEnd: SparkListenerApplicationEnd => listener.onApplicationEnd(applicationEnd) 19 case metricsUpdate: SparkListenerExecutorMetricsUpdate => listener.onExecutorMetricsUpdate(metricsUpdate) 20 case executorAdded: SparkListenerExecutorAdded => listener.onExecutorAdded(executorAdded) 21 case executorRemoved: SparkListenerExecutorRemoved => listener.onExecutorRemoved(executorRemoved) 22 case executorBlacklistedForStage: SparkListenerExecutorBlacklistedForStage => listener 23 .onExecutorBlacklistedForStage(executorBlacklistedForStage) 24 case nodeBlacklistedForStage: SparkListenerNodeBlacklistedForStage => listener.onNodeBlacklistedForStage(nodeBlacklistedForStage) 25 case executorBlacklisted: SparkListenerExecutorBlacklisted => listener.onExecutorBlacklisted(executorBlacklisted) 26 case executorUnblacklisted: SparkListenerExecutorUnblacklisted => listener.onExecutorUnblacklisted(executorUnblacklisted) 27 case nodeBlacklisted: SparkListenerNodeBlacklisted => listener.onNodeBlacklisted(nodeBlacklisted) 28 case nodeUnblacklisted: SparkListenerNodeUnblacklisted => listener.onNodeUnblacklisted(nodeUnblacklisted) 29 case blockUpdated: SparkListenerBlockUpdated => listener.onBlockUpdated(blockUpdated) 30 case speculativeTaskSubmitted: SparkListenerSpeculativeTaskSubmitted => listener.onSpeculativeTaskSubmitted(speculativeTaskSubmitted) 31 case _ => listener.onOtherEvent(event) 32 } 33 }
【Listener如何被通知到? stage7】
所以,只要 AsyncEventQueue 中的 eventQueue 队列中一直有事件,就会一直被消费通知到对应的方法中,注意,这里的 eventQueue 是 AsyncEventQueue 的 eventQueue ,和上面的 LiveListenerBus 的 eventQueue 是两码事,不要弄混了
那么什么时候会 往 AsyncEventQueue 的 eventQueue 队列增加event事件呢?
在 AsyncEventQueue 的post( event ) 方法中,就会增加
org.apache.spark.scheduler.AsyncEventQueue#post
1 def post(event: SparkListenerEvent): Unit = { 2 ... 3 4 if (eventQueue.offer(event)) { 5 return 6 } 7 8 ... 9 }
【Listener如何被通知到? stage8】
谁会调 AsyncEventQueue 的 post( event ) 方法呢?
在【Listener如何被通知到? stage1】 中的
1 queuedEvents.foreach(q.post)
在 org.apache.spark.scheduler.LiveListenerBus#post 下也会调用
1 val it = queues.iterator() 2 while (it.hasNext()) { 3 it.next().post(event) 4 }