spark事件总线的核心是LiveListenerBus,其内部维护了多个AsyncEventQueue队列用于存储和分发和响应SparkListenerEvent事件。
spark事件总线整体思想是生产消费者模式,消息事件实现了先进先出和异步投递,同时将事件的产生(例如DAGScheduler创建stage、提交job)和事件的处理(例如在Spark UI显示任务信息)分离,在一定程度上提升了系统的异步处理性能,是一个经典的事件处理模式。
一、LiveListenerBus 总体结构及监听器介绍
- 在queues:CopyOnWriteArrayList[AsyncEventQueue]中,目前主要有4种命名队列(AsyncEventQueue),对应了4个addToXXXXQueue()方法。由图中左侧可知,每种命名队列有1~2个不同SparkListenerInterface实现,总共有大约10种不同功能listener添加到总线中。
- addToSharedQueue(),主要由SparkContext调用,即用户可以在代码中增加Listener,或从配单中增加Listener并反射调用[实现在setupAndStartListenerBus()]
- addToManagementQueue(),分别可增加HeartbeatReceiver(用于监听Executor的Add和Remove,并使用线程定期判断各Executor的心跳时间,超时则Kill
Executor),另外可通过ExecutorAllocationManager增加ExecutorAllocationListener(通过计算总task数和Excutor并行度进行匹配,动态增加、减少Executor,需要配置,默认关闭)- addToStatusQueue(),主要增加了AppStatusListener,为AppStatusStore提供Job、Stage、Task的UI展示数据,以及增加了SQLAppStatusListener,为SQLAppStatesStore提供SQL
UI展示数据,在本文后续章节KVStore中介绍- addToEventLogQueue(),将监听到的事件以Json方式写出到日志存储,需要配置,默认为关闭
- 前面几个方法内部均调用addToQueue()方法,还有一种情况,spark structured streaming流式计算对应的StreamingQueryListenerBus通过addToQueue()方法加入"streams"队列(用于监听流的start、process、terminate时间,其中process事件能获取到流处理的详细进度,包括流名称、id、水印时间、source offsets、sink offsets等)。
private[spark] class LiveListenerBus(conf: SparkConf) {
...
private val queues = new CopyOnWriteArrayList[AsyncEventQueue]()
@volatile private[scheduler] var queuedEvents = new mutable.ListBuffer[SparkListenerEvent]()
def addToSharedQueue(listener: SparkListenerInterface): Unit = {
addToQueue(listener, SHARED_QUEUE)
}
def addToManagementQueue(listener: SparkListenerInterface): Unit = {
addToQueue(listener, EXECUTOR_MANAGEMENT_QUEUE)
}
def addToStatusQueue(listener: SparkListenerInterface): Unit = {
addToQueue(listener, APP_STATUS_QUEUE)
}
def addToEventLogQueue(listener: SparkListenerInterface): Unit = {
addToQueue(listener, EVENT_LOG_QUEUE)
}
// addToQueue实际上是调用了具体某个命名Queue的addListener()
private[spark] def addToQueue(
listener: SparkListenerInterface,
queue: String): Unit = synchronized {
...
queues.asScala.find(_.name == queue) match {
case Some(queue) =>
queue.addListener(listener)
case None =>
val newQueue = new AsyncEventQueue(queue, conf, metrics, this)
newQueue.addListener(listener)
if (started.get()) {
newQueue.start(sparkContext)
}
queues.add(newQueue)
}
}
...
}