Apache Spark源码走读之21 -- WEB UI和Metrics初始化及数据更新过程分析
欢迎转载,转载请注明出处,徽沪一郎.
概要
WEB UI和Metrics子系统为外部观察监测Spark内部运行情况提供了必要的窗口,本文将简略的过一下其内部代码实现。
WEB UI
先上图感受一下spark webui 假设当前已经在本机运行standalone cluster模式,输入http://127.0.0.1:8080将会看到如下页面
driver application默认会打开4040端口进行http监听,可以看到application相关的详细信息
显示每个stage的详细信息
启动过程
本节要讨论的重点是http server是如何启动的,页面中的数据是从哪里获取到的?Spark中用到的http server是jetty, jetty采用java编写,是非常轻巧的servlet engine和http server。能够嵌入到用户程序中执行,不用像tomcat或jboss那样需要自己独立的jvm进程。
SparkUI在SparkContext初始化的时候创建
// Initialize the Spark UI , registering all
associated listeners
private [spark] val ui = new SparkUI (this)
ui.bind ()
initialize的主要工作是注册页面处理句柄,WebUI的子类需要实现自己的initialize函数
bind将真正启动jetty server.
def bind () {
assert (! serverInfo .isDefined , " Attempted to bind %
s more than once!". format ( className ))
try {
// 启 动 JettyServer
serverInfo = Some( startJettyServer (" 0.0.0.0 ",
port , handlers , conf))
logInfo (" Started %s at http ://%s:%d". format (
className , publicHostName , boundPort ))
} catch {
case e: Exception =>
logError (" Failed to bind %s". format ( className )
, e)
System .exit (1)
}
}
在startJettyServer函数中将JettyServer运行起来的关键处理函数是connect
def connect(currentPort: Int): (Server, Int) = {
val server = new Server(new InetSocketAddress(hostName, currentPort))
val pool = new QueuedThreadPool
pool.setDaemon(true)
server.setThreadPool(pool)
server.setHandler(collection)
Try {
server.start()
} match {
case s: Success[_] =>
(server, server.getConnectors.head.getLocalPort)
case f: Failure[_] =>
val nextPort = (currentPort + 1) % 65536
server.stop()
pool.stop()
val msg = s"Failed to create UI on port $currentPort. Trying again on port $nextPort."
if (f.toString.contains("Address already in use")) {
logWarning(s"$msg - $f")
} else {
logError(msg, f.exception)
}
connect(nextPort)
}
}
val (server, boundPort) = connect(port)
ServerInfo(server, boundPort, collection)
}
数据获取
页面中的数据是如何获取的呢,这就要归功于SparkListener了,典型的观察者设计模式。当有与stage及task相关的事件发生时,这些Listener都将收到通知,并进行数据更新。
需要指出的是,数据尽管得以自动更新,但页面并没有,还是需要手工刷新才能得到最新的数据。
上图显示的是SparkUI中注册了哪些SparkListener子类。来看一看这些子类是在什么时候注册进去的, 注意研究一下SparkUI.initialize函
def initialize() {
listenerBus.addListener(storageStatusListener)
val jobProgressTab = new JobProgressTab(this)
attachTab(jobProgressTab)
attachTab(new StorageTab(this))
attachTab(new EnvironmentTab(this))
attachTab(new ExecutorsTab(this))
attachHandler(createStaticHandler(SparkUI.STATIC_RESOURCE_DIR, "/static"))
attachHandler(createRedirectHandler("/", "/stages", basePath = basePath))
attachHandler(
createRedirectHandler("/stages/stage/kill", "/stages", jobProgressTab.handleKillRequest))
if (live) {
sc.env.metricsSystem.getServletHandlers.foreach(attachHandler)
}
}
举一个实际例子来看看Notifier发送Event的时刻,比如有任务提交的时 resourceOffer->taskStarted->handleBeginEvent
private [ scheduler ] def handleBeginEvent (task: Task[_
], taskInfo : TaskInfo ) {
listenerBus .post( SparkListenerTaskStart (task.
stageId , taskInfo ))
submitWaitingStages ()
}
post其实是向listenerBus的消息队列中添加一个消息,真正将消息发送 出去的时另一个处理线程listenerThread
override def run (): Unit = Utils.
logUncaughtExceptions {
while (true) {
eventLock . acquire ()
// Atomically remove and process this event
LiveListenerBus .this. synchronized {
val event = eventQueue .poll
if (event == SparkListenerShutdown ) {
// Get out of the while loop and shutdown
the daemon thread
return
}
Option (event). foreach ( postToAll )
}
}
}
Option(event).foreach(postToAll)负责将事件通知给各个Observer.postToAll的函数实现如下
def postToAll(event: SparkListenerEvent) {
event match {
case stageSubmitted: SparkListenerStageSubmitted =>
foreachListener(_.onStageSubmitted(stageSubmitted))
case stageCompleted: SparkListenerStageCompleted =>
foreachListener(_.onStageCompleted(stageCompleted))
case jobStart: SparkListenerJobStart =>
foreachListener(_.onJobStart(jobStart))
case jobEnd: SparkListenerJobEnd =>
foreachListener(_.onJobEnd(jobEnd))
case taskStart: SparkListenerTaskStart =>
foreachListener(_.onTaskStart(taskStart))
case taskGettingResult: SparkListenerTaskGettingResult =>
foreachListener(_.onTaskGettingResult(taskGettingResult))
case taskEnd: SparkListenerTaskEnd =>
foreachListener(_.onTaskEnd(taskEnd))
case environmentUpdate: SparkListenerEnvironmentUpdate =>
foreachListener(_.onEnvironmentUpdate(environmentUpdate))
case blockManagerAdded: SparkListenerBlockManagerAdded =>
foreachListener(_.onBlockManagerAdded(blockManagerAdded))
case blockManagerRemoved: SparkListenerBlockManagerRemoved =>
foreachListener(_.onBlockManagerRemoved(blockManagerRemoved))
case unpersistRDD: SparkListenerUnpersistRDD =>
foreachListener(_.onUnpersistRDD(unpersistRDD))
case applicationStart: SparkListenerApplicationStart =>
foreachListener(_.onApplicationStart(applicationStart))
case applicationEnd: SparkListenerApplicationEnd =>
foreachListener(_.onApplicationEnd(applicationEnd))
case SparkListenerShutdown =>
}
}
Metrics
在系统设计中,测量模块是不可或缺的组成部分。通过这些测量数据来感知系统的运行情况。
在Spark中,测量模块由MetricsSystem来担任,MetricsSystem中有三个重要的概念,分述如下。
- instance 表示谁在使用metrics system, 目前已知的有master, worker, executor和client driver会创建metrics system用以测量
- source 表示数据源,从哪里获取数据
- sinks 数据目的地,将从source获取的数据发送到哪
Spark目前支持将测量数据保存或发送到如下目的地
- ConsoleSink 输出到console
- CSVSink 定期保存成为CSV文件
- JmxSink 注册到JMX,以通过JMXConsole来查看
- MetricsServlet 在SparkUI中添加MetricsServlet用以查看Task运行时的测量数据
- GraphiteSink 发送给Graphite以对整个系统(不仅仅包括spark)进行监控
下面从MetricsSystem的创建,数据源的添加,数据更新与发送几个方面来跟踪一下源码。
初始化过程
MetricsSystem依赖于由codahale提供的第三方库Metrics,可以在metrics.codahale.com找到更为详细的介绍。
以Driver Application为例,driver application首先会初始化SparkContext,在SparkContext的初始化过程中就会创建MetricsSystem,具体调用关系如下。 SparkContext.init->SparkEnv.init->MetricsSystem.createMetricsSystem
注册数据源,继续以SparkContext为例
private val dagSchedulerSource = new DAGSchedulerSource(this.dagScheduler, this)
private val blockManagerSource = new BlockManagerSource(SparkEnv.get.blockManager, this)
private def initDriverMetrics() {
SparkEnv.get.metricsSystem.registerSource(dagSchedulerSource)
SparkEnv.get.metricsSystem.registerSource(blockManagerSource)
}
initDriverMetrics()
数据读取
数据读取由Sink来完成,在Spark中创建的Sink子类如下图所示
读取最新的数据,以CsvSink为例,最主要的就是创建CsvReporter,启动之后会定期更新最近的数据到console。不同类型的Sink所使用的Reporter是不一样的。
val reporter: CsvReporter = CsvReporter.forRegistry(registry)
.formatFor(Locale.US)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.convertRatesTo(TimeUnit.SECONDS)
.build(new File(pollDir))
override def start() {
reporter.start(pollPeriod, pollUnit)
}
Spark中关于metrics子系统的配置文件详见conf/metrics.properties. 默认的Sink是MetricsServlet,在任务提交执行之后,输入http://127.0.0.1:4040/metrics/json会得到以json格式保存的metrics信息。
Apache Spark源码走读之21 -- WEB UI和Metrics初始化及数据更新过程分析的更多相关文章
- Apache Spark源码走读之7 -- Standalone部署方式分析
欢迎转载,转载请注明出处,徽沪一郎. 楔子 在Spark源码走读系列之2中曾经提到Spark能以Standalone的方式来运行cluster,但没有对Application的提交与具体运行流程做详细 ...
- Apache Spark源码走读之13 -- hiveql on spark实现详解
欢迎转载,转载请注明出处,徽沪一郎 概要 在新近发布的spark 1.0中新加了sql的模块,更为引人注意的是对hive中的hiveql也提供了良好的支持,作为一个源码分析控,了解一下spark是如何 ...
- Apache Spark源码走读之16 -- spark repl实现详解
欢迎转载,转载请注明出处,徽沪一郎. 概要 之所以对spark shell的内部实现产生兴趣全部缘于好奇代码的编译加载过程,scala是需要编译才能执行的语言,但提供的scala repl可以实现代码 ...
- Apache Spark源码走读之23 -- Spark MLLib中拟牛顿法L-BFGS的源码实现
欢迎转载,转载请注明出处,徽沪一郎. 概要 本文就拟牛顿法L-BFGS的由来做一个简要的回顾,然后就其在spark mllib中的实现进行源码走读. 拟牛顿法 数学原理 代码实现 L-BFGS算法中使 ...
- Apache Spark源码走读之18 -- 使用Intellij idea调试Spark源码
欢迎转载,转载请注明出处,徽沪一郎. 概要 上篇博文讲述了如何通过修改源码来查看调用堆栈,尽管也很实用,但每修改一次都需要编译,花费的时间不少,效率不高,而且属于侵入性的修改,不优雅.本篇讲述如何使用 ...
- Apache Spark源码走读之6 -- 存储子系统分析
欢迎转载,转载请注明出处,徽沪一郎. 楔子 Spark计算速度远胜于Hadoop的原因之一就在于中间结果是缓存在内存而不是直接写入到disk,本文尝试分析Spark中存储子系统的构成,并以数据写入和数 ...
- Apache Spark源码走读之5 -- DStream处理的容错性分析
欢迎转载,转载请注明出处,徽沪一郎,谢谢. 在流数据的处理过程中,为了保证处理结果的可信度(不能多算,也不能漏算),需要做到对所有的输入数据有且仅有一次处理.在Spark Streaming的处理机制 ...
- Apache Spark源码走读之17 -- 如何进行代码跟读
欢迎转载,转载请注明出处,徽沪一郎 概要 今天不谈Spark中什么复杂的技术实现,只稍为聊聊如何进行代码跟读.众所周知,Spark使用scala进行开发,由于scala有众多的语法糖,很多时候代码跟着 ...
- Apache Spark源码走读之11 -- sql的解析与执行
欢迎转载,转载请注明出处,徽沪一郎. 概要 在即将发布的spark 1.0中有一个新增的功能,即对sql的支持,也就是说可以用sql来对数据进行查询,这对于DBA来说无疑是一大福音,因为以前的知识继续 ...
随机推荐
- 如何识别是visual studio下头的哪种类型程序
可以通过文件来判断 比如MFC, 那它就会包括xxxview.cpp文件. win32又分为win32项目和console(即控制台应用程序),看主函数 win32控制台应用程序的主函数为_tmain ...
- Struts文件上传allowedTypes问题,烦人的“允许上传的文件类型”
Struts的文件上传问题,相信很多人都会使用allowedTypes参数来配置允许上传的文件类型,如下. <param name="allowedTypes"> im ...
- linux 小喇叭 没了
Waiting for sound system to respond 方法: # pulseaudio --start -D W: main.c: This program is not inten ...
- Spring MVC笔记 使用JdbcTemplate
Spring提供了 JdbcTemplate 来封装数据库jdbc操作细节, 包括: 数据库连接[打开/关闭] ,异常转义 ,SQL执行 ,查询结果的转换, 其中体现了 模板模式 的设计模式思想. 使 ...
- oracle体系结构详细示意图
- C# 读取本地图片 转存到其他盘符
UpFileContent upfile = new UpFileContent(); upfile.StationImageName = "123.png"; FileStrea ...
- Java程序员最常用的8个Java日志框架
转自:http://www.codeceo.com/article/8-java-log-framework.html 作为一名Java程序员,我们开发了很多Java应用程序,包括桌面应用.WEB应用 ...
- Action类为何要 extends ActionSupport
我做的时候,我的action是继承ActionSupport的 Struts 2的Action无须实现任何接口或继承任何类型,普通的POJO类就可以用做Action类,但是,我们为了方便实现Actio ...
- Messenger信使
* Messenger用于Activity和Service之间消息传递 需求:activity来绑定服务,绑定成功后,将数据发送给service,service收到消息后,再发送信息给activity ...
- WebView的写法
public class MainActivity extends Activity { private WebView wv; @Override protected void onCreate(B ...