【Spark内核源码】事件总线ListenerBus

目录

消息总线ListenerBus

异步事件处理LiveListenerBus

增加事件

listenerThread处理事件


消息总线ListenerBus

org.apache.spark.util.ListenerBus处理来自DAGScheduler、SparkContext、BlockManagerMasterEndpoint等组件的事件,是整个Spark框架体系内事件处理总线。

ListenerBus是消息总线特质(不懂特质的看这里scala特质 ),提供了增加、删除方法以及事件执行监听器的方法。监听列表listeners是线程安全的,但是postToAll事件执行监听器的方法却是线程不安全的,所以该方法要在同一线程中执行。

ListenerBus特质源码

 既然是特质,那么就一定会有实现类,ListenerBus的继承结构如下:

ListenerBus继承关系

 

异步事件处理LiveListenerBus

org.apache.spark.scheduler.LiveListenerBus是一个核心的具体实现,LiveListenerBus实现了异步事件处理,内置事件阻塞队列,可通过“spark.scheduler.listenerbus.eventqueue.size”设置,默认1000。

事件队列的设置

增加事件

既然有阻塞队列,那么就需要有将事件增加到队列中的方法,post(event: SparkListenerEvent)就是干这事的。该方法中传递一个SparkListenerEvent作为参数,在将事件增加到队列中之前,需要先判断LinveListenerBus是否还活着,如果stop了,则报错并返回。如果不处于stop状态,则增加到阻塞队列中,增加成功了释放信号,告诉别人我增加好了。如果由于队列已经满了增加失败,则会移除事件,并在删除事件计数器中进行自增记录,如果上一次删除失败事件和本次删除失败事件的时间间隔在60秒以上,就输出一次错误报告。流程如下:

增加event到队列的流程

代码如下:

/**
    * 将事件增加到eventQueue事件队列中去
    * */
  def post(event: SparkListenerEvent): Unit = {
    // 判断LiveListenerBus是否停止,停止就报错并返回
    if (stopped.get) {
      // Drop further events to make `listenerThread` exit ASAP
      logError(s"$name has already stopped! Dropping event $event")
      return
    }
    // 向队列中增加事件
    val eventAdded = eventQueue.offer(event)
    /**
      * 增加成功,释放信号量,加快listenerThread工作,
      * 在listenerTread中eventLock.acquire()一直在获取信号量
      *
      * 增加失败,则移除事件,并对删除事件计数器droppedEventsCounter自增
      * */
    if (eventAdded) {
      eventLock.release()
    } else {
      onDropEvent(event)
      droppedEventsCounter.incrementAndGet()
    }

    // 如果有失败的时间,60秒打印一次日志
    val droppedEvents = droppedEventsCounter.get
    if (droppedEvents > 0) {
      // Don't log too frequently
      if (System.currentTimeMillis() - lastReportTimestamp >= 60 * 1000) {
        // There may be multiple threads trying to decrease droppedEventsCounter.
        // Use "compareAndSet" to make sure only one thread can win.
        // And if another thread is increasing droppedEventsCounter, "compareAndSet" will fail and
        // then that thread will update it.
        if (droppedEventsCounter.compareAndSet(droppedEvents, 0)) {
          val prevLastReportTimestamp = lastReportTimestamp
          lastReportTimestamp = System.currentTimeMillis()
          logWarning(s"Dropped $droppedEvents SparkListenerEvents since " +
            new java.util.Date(prevLastReportTimestamp))
        }
      }
    }
  }

上面提到增加成功之后释放信号量,既然有释放信号量,那么就一定会有接收信号量,在listenerThread中接收信号量并处理新增加的也就是未处理的事件。增加成功后立刻释放信号量这个操作的好处就在于,只要增加成功就立刻处理,不需要管后面的操作。

listenerThread处理事件

下面就看一下listenerThread是如何处理事件的,listenerThread代码如下:

private val listenerThread = new Thread(name) {
    setDaemon(true)
    override def run(): Unit = Utils.tryOrStopSparkContext(sparkContext) {
      LiveListenerBus.withinListenerThread.withValue(true) {
        while (true) {
          eventLock.acquire()// 获取信号量
          self.synchronized {
            processingEvent = true
          }
          try {
            // 从队列中获取事件
            val event = eventQueue.poll 
            if (event == null) {
              // Get out of the while loop and shutdown the daemon thread
              if (!stopped.get) {
                throw new IllegalStateException("Polling `null` from eventQueue means" +
                  " the listener bus has been stopped. So `stopped` must be true")
              }
              return
            }
            postToAll(event) // 处理事件
          } finally {
            self.synchronized {
              processingEvent = false
            }
          }
        }
      }
    }
  }

Utils.tryOrStopSparkContext(sparkContext)方法会保证在线程中抛出异常后,会有启动一个新的线程来停止SparkContext。

线程会不断获取信号量,获取到信号量,说明还有事件未处理,并同步设置processingEvent状态,表示我正在执行任务。然后从事件队列中获取事件,并执行事件监听器,处理事件postToAll(event)的方法是LiveListenerBus的超类ListenerBus中的方法。执行过后同步设置processingEvent状态,表示我执行完任务了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值