目录
消息总线ListenerBus
org.apache.spark.util.ListenerBus处理来自DAGScheduler、SparkContext、BlockManagerMasterEndpoint等组件的事件,是整个Spark框架体系内事件处理总线。
ListenerBus是消息总线特质(不懂特质的看这里scala特质 ),提供了增加、删除方法以及事件执行监听器的方法。监听列表listeners是线程安全的,但是postToAll事件执行监听器的方法却是线程不安全的,所以该方法要在同一线程中执行。
![](https://i-blog.csdnimg.cn/blog_migrate/2f2f4358dd3188e7ae513ba280723e45.png)
既然是特质,那么就一定会有实现类,ListenerBus的继承结构如下:
![](https://i-blog.csdnimg.cn/blog_migrate/0cbd6d5a72691d0611ccc6a50c4b19ea.png)
异步事件处理LiveListenerBus
org.apache.spark.scheduler.LiveListenerBus是一个核心的具体实现,LiveListenerBus实现了异步事件处理,内置事件阻塞队列,可通过“spark.scheduler.listenerbus.eventqueue.size”设置,默认1000。
![](https://i-blog.csdnimg.cn/blog_migrate/589a12064f11f6e7bf413c8d7e1e70c6.png)
增加事件
既然有阻塞队列,那么就需要有将事件增加到队列中的方法,post(event: SparkListenerEvent)就是干这事的。该方法中传递一个SparkListenerEvent作为参数,在将事件增加到队列中之前,需要先判断LinveListenerBus是否还活着,如果stop了,则报错并返回。如果不处于stop状态,则增加到阻塞队列中,增加成功了释放信号,告诉别人我增加好了。如果由于队列已经满了增加失败,则会移除事件,并在删除事件计数器中进行自增记录,如果上一次删除失败事件和本次删除失败事件的时间间隔在60秒以上,就输出一次错误报告。流程如下:
![](https://i-blog.csdnimg.cn/blog_migrate/6edf64db0dd899614796b112de27d104.png)
代码如下:
/**
* 将事件增加到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状态,表示我执行完任务了。