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) 这句代码什么意思?
  可以参考文章:https://www.jianshu.com/p/7a8387735316
 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 }

 

 
 

posted @ 2022-01-09 14:40  白羊座怪蜀黍  阅读(361)  评论(0编辑  收藏  举报