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
}
}
到此,整个观察者模式的巨无霸就写完了。