SparkUI之LiveListenerBus

在这里插入图片描述
SparkUI的LiveListenerBus是一个非常好的观察者模式的一个实现。 LiveListener接收来自DAGSchedular发送过来的SparkListenerEvent事件,将event发送给注册了的观察者,也就是众多的Listener, 这些Listener会把event中的数据解析并在UI 的tab上显示出来。

上图左边就是观察者们。

SparkListenerInterface是一个接口,定义了许多针对不同的event的处理方法。

private[spark] trait SparkListenerInterface {

  /**
   * Called when a stage completes successfully or fails, with information on the completed stage.
   */
  def onStageCompleted(stageCompleted: SparkListenerStageCompleted): Unit

  /**
   * Called when a stage is submitted
   */
  def onStageSubmitted(stageSubmitted: SparkListenerStageSubmitted): Unit

  /**
   * Called when a task starts
   */
  def onTaskStart(taskStart: SparkListenerTaskStart): Unit

  /**
   * Called when a task begins remotely fetching its result (will not be called for tasks that do
   * not need to fetch the result remotely).
   */
  def onTaskGettingResult(taskGettingResult: SparkListenerTaskGettingResult): Unit

...

SparkListener是一个抽象类,实现了SparkListenerInterface, 众多的Listener只需要继承自这个类并且实现各自关心的event的处理方法就好了

abstract class SparkListener extends SparkListenerInterface {
  override def onStageCompleted(stageCompleted: SparkListenerStageCompleted): Unit = { }

  override def onStageSubmitted(stageSubmitted: SparkListenerStageSubmitted): Unit = { }

  override def onTaskStart(taskStart: SparkListenerTaskStart): Unit = { }

  override def onTaskGettingResult(taskGettingResult: SparkListenerTaskGettingResult): Unit = { }
...

通常的观察者模式,定义一个Subject, 持有一个观察者的List, 当事件更新时,遍历list, 调用每一个观察者的update方法通知他们。这里的观察者模式相对比较复杂,事件的种类比较多,每种事件要发配给不同种类的观察者,同时,事件的接收和发送是并发的和异步的。ListenerBus就是一个持有观察者的接口,定义为CopyOnWriteArrayList[Listener], 并且支持addListener和removeListener, 定义了一个postToAll(e:Event)的方法,遍历list, 对每个观察者调用doPostevent(l:listener, e:eEvent)方法,doPostEvent方法是抽象的,需要其实现类自己来完成。

ListenerBus.scala部分代码:

/**
 * Post the event to all registered listeners. The `postToAll` caller should guarantee calling
 * `postToAll` in the same thread for all events.
 */
def postToAll(event: E): Unit = {
  val iter = listenersPlusTimers.iterator
  while (iter.hasNext) {
    val listenerAndMaybeTimer = iter.next()
    val listener = listenerAndMaybeTimer._1
    val maybeTimer = listenerAndMaybeTimer._2
    val maybeTimerContext = if (maybeTimer.isDefined) {
      maybeTimer.get.time()
    } else {
      null
    }
    try {
      doPostEvent(listener, event)
      if (Thread.interrupted()) {
        // We want to throw the InterruptedException right away so we can associate the interrupt
        // with this listener, as opposed to waiting for a queue.take() etc. to detect it.
        throw new InterruptedException()
      }
    } catch {
      case ie: InterruptedException =>
        logError(s"Interrupted while posting to ${Utils.getFormattedClassName(listener)}.  " +
          s"Removing that listener.", ie)
        removeListenerOnError(listener)
      case NonFatal(e) if !isIgnorableException(e) =>
        logError(s"Listener ${Utils.getFormattedClassName(listener)} threw an exception", e)
    } finally {
      if (maybeTimerContext != null) {
        maybeTimerContext.stop()
      }
    }
  }
}

/**
 * Post an event to the specified listener. `onPostEvent` is guaranteed to be called in the same
 * thread for all listeners.
 */
protected def doPostEvent(listener: L, event: E): Unit

SparkListenerBus实现了ListenerBus,并且重写了doPostEvent方法,在这个方法里,针对event的不同类型,调用listener的相应方法来处理。

private[spark] trait SparkListenerBus
  extends ListenerBus[SparkListenerInterface, SparkListenerEvent] {
  protected override def doPostEvent(
      listener: SparkListenerInterface,
      event: SparkListenerEvent): Unit = {
    event match {
      case stageSubmitted: SparkListenerStageSubmitted =>
        listener.onStageSubmitted(stageSubmitted)
      case stageCompleted: SparkListenerStageCompleted =>
        listener.onStageCompleted(stageCompleted)
      case jobStart: SparkListenerJobStart =>
        listener.onJobStart(jobStart)
      case jobEnd: SparkListenerJobEnd =>
        listener.onJobEnd(jobEnd)
      case taskStart: SparkListenerTaskStart =>
        listener.onTaskStart(taskStart)
      case taskGettingResult: SparkListenerTaskGettingResult =>
        listener.onTaskGettingResult(taskGettingResult)
      ...
      case _ => listener.onOtherEvent(event)
    }
  }

}

AsyncEventQueue是一个异步的事件处理队列,继承自SparkListenerBus。 为什么不直接使用SparkListenerBus,非要再继承一遍呢?是因为事件接受和处理的过程必须是异步的,这样时间的发送和接收就不需要等待,放在事件队列里面就可以了,这样相当于做了一个隔离和缓冲。AsyncEventQueue持有一个LinkedBlockingQueue[SparkListenerEvent]的对象,使用的时候会启动一个守护线程,该线程会负责将事件队列中的事件依次发送给每一个listener, 当事件队列空的时候,线程任务会阻塞。当主线程接收到新的任务的时候,会直接把事件添加到事件队列中,守护线程自动消费即可。 线程移动之后执行的方法封装在dispatch中。
AsyncEventQueue.scala部分代码

// 守护线程
private val dispatchThread = new Thread(s"spark-listener-group-$name") {
  setDaemon(true)
  override def run(): Unit = Utils.tryOrStopSparkContext(sc) {
    dispatch()
  }
}

private def dispatch(): Unit = LiveListenerBus.withinListenerThread.withValue(true) {
  var next: SparkListenerEvent = eventQueue.take()
  while (next != POISON_PILL) {
    val ctx = processingTime.time()
    try {
      super.postToAll(next)
    } finally {
      ctx.stop()
    }
    eventCount.decrementAndGet()
    next = eventQueue.take()
  }
  eventCount.decrementAndGet()
}

最后一个是LiveListenerBus, 也就是最终的大boss, 它是真正接口DAGSchedular的Event事件,并且通知各个Listener。按照Listener的不同类型,提供了4中不同的队列,存储在一个数组里面,CopyOnWriteArrayList[AsyncEventQueue], 每一个队列都是一个AsyncEventQueue的对象,能够添加listener并且启动一个守护线程消费接收到的event。 LiveListenerBus接收到的event也会存储在一个队列里面: mutable.ListBuffer[SparkListenerevent]对象,LiveListenerBus启动的时候,会先启动各个 AsyncEventQueue, 然后将自身的ListBuffer中的事件,逐个发送给每一个 AsyncEventQueue。 当向LiveListenerBus添加event的时候,如果LiveListenerBus已经启动,则直接调用postToQueues的方法,发送事件到所有eventQueue,;如果未启动,则通过加锁的方式,添加到ListBuffer中。

/** Post an event to all queues. */
def post(event: SparkListenerEvent): Unit = {
  if (stopped.get()) {
    return
  }

  metrics.numEventsPosted.inc()

  // If the event buffer is null, it means the bus has been started and we can avoid
  // synchronization and post events directly to the queues. This should be the most
  // common case during the life of the bus.
  if (queuedEvents == null) {
    postToQueues(event)
    return
  }

  // Otherwise, need to synchronize to check whether the bus is started, to make sure the thread
  // calling start() picks up the new event.
  synchronized {
    if (!started.get()) {
      queuedEvents += event
      return
    }
  }

到此,整个观察者模式的巨无霸就写完了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值