HistoryServer服务可以让用户通过Spark UI界面,查看历史应用(已经执行完的应用)的执行细节,比如job信息、stage信息、task信息等,该功能是基于spark eventlogs日志文件的,所以必须打开eventlogs日志开关,关于日志开关的打开和HistoryServer服务的启动方法这里不再讲述,下面进入正题

下面使用的spark版本是2.0.2

类结构图 
Web相关 
 
数据流相关 

相关类及特质 
WebUI 
Web Server服务中UI层次结构的最顶层。每一个WebUI包含了一个tabs的集合,而每一个tab又包含了一个pages的集合。tabs页是可选的,而且WebUI也可以直接添加page 
继承该特质的有SparkUI、MasterWebUI、WorkerWebUI和HistoryServer,在这里我们主要介绍HistoryServer

WebUITab 
一个tab包含了一个pages的集合。prefix通过追加到parent的url组成一个完整的url path,而且不能包含斜杠 
继承该特质有JobsTab、StagesTab、ExecutorsTab、StorageTab等(这里没有列全),对应于Spark UI界面上的Jobs、Stages、Executors、Storage等Tab页

WebUIPage 
一个page表示UI层次结构中的叶子节点。WebUIPage的直接父类即可以是WebUI,也可以是WebUITab。 
如果父类是WebUI,prefix追加到parent的url形成完整的url path,如果父类是WebUITab,prefix追加到parent的prefix形成一个相对url path。Prefix中不能包含斜杠 
继承该特质的有JobPage、StagePage、ExecutionPage、StoragePage等,对应于Tab页中具体的Page

HistoryPage 
继承至WebUIPage,通过render函数渲染生成history页面

UIRoot 
该特质被根容器(HistoryServer、SparkUI)继承,用来为它们提供获取application信息的统一接口

HistoryServer

def main(argStrings: Array[String]): Unit = {
……
val providerName = conf.getOption("spark.history.provider")
.getOrElse(classOf[FsHistoryProvider].getName())
val provider = Utils.classForName(providerName)
.getConstructor(classOf[SparkConf])
.newInstance(conf)
.asInstanceOf[ApplicationHistoryProvider]
val port = conf.getInt("spark.history.ui.port", 18080)
val server = new HistoryServer(conf, provider, securityManager, port)
server.bind()
ShutdownHookManager.addShutdownHook { () => server.stop() }
// Wait until the end of the world... or if the HistoryServer process is manually stopped
while(true) { Thread.sleep(Int.MaxValue) }
}

HistoryServer继承至WebUI,启动的时候,会将环境配置以及provider作为成员变量来初始化HistoryServer实例,其中provider用来提供application的信息供web展示使用,HistoryServer实例化后执行bind()函数,启动jetty,将HTTP服务与web接口绑定,这时候historyserver web服务已经启动了,之后添加了关闭server钩子函数后进入无限循环等待

在HistoryServer实例化的过程中,会执行initialize()函数,

def initialize() {
attachPage(new HistoryPage(this))
attachHandler(ApiRootResource.getServletHandler(this))
attachHandler(createStaticHandler(SparkUI.STATIC_RESOURCE_DIR, "/static"))
val contextHandler = new ServletContextHandler
contextHandler.setContextPath(HistoryServer.UI_PATH_PREFIX)
contextHandler.addServlet(new ServletHolder(loaderServlet), "/*")
attachHandler(contextHandler)
}

在该函数中,首先通过attachPage函数在UI中添加了HistoryPage实例,该实例负责渲染生成history page,然后通过attachHandler添加了不同的handler,可以访问url路由获取对应的信息,其中ApiRootResource提供了api/vi/开头的路由,通过该路由,history page可以获取后台解析出的eventlog信息用以呈现,数据通过UIRoot提供的接口获取

到这里,HistoryServer的Web端基本构建完成

HsitoryServer数据缓存及获取 
数据缓存主要通过使用google缓存机制LoadingCache实现,关于LoadingCache在Spark HistoryServer中的运用在另外一篇文章中分析

FsHistoryProvider 
前面完成了web结构的构建,接下来就需要提供接口获取历史application的信息来呈现,而FsHistoryProvider就是这个接口,作为成员变量传递给HistoryServer。这个类在实例化的时候,执行了initialize()函数,在该函数中,首先会检查hdfs是否处于安全模式,如果处于安全模式,则会等待至退出安全模式,如果不处于安全模式,则走进startPolling函数,在该函数中会读取配置的eventlog路径(默认为file:/tmp/spark-events,通过spark.history.fs.logDirectory配置),然后启动一个线程不断扫描该路径下的eventlog文件,将文件解析后加载到内存中供web查询使用,相关函数如下:

private val pool = Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder()
.setNameFormat("spark-history-task-%d").setDaemon(true).build())
pool.scheduleWithFixedDelay(getRunner(checkForLogs), 0, UPDATE_INTERVAL_S, TimeUnit.SECONDS)

if (conf.getBoolean("spark.history.fs.cleaner.enabled", false)) {
pool.scheduleWithFixedDelay(getRunner(cleanLogs), 0, CLEAN_INTERVAL_S, TimeUnit.SECONDS)
}

另外如果配置了清理开关(默认一天清理一次),则会清理内存中超时的application信息,并删除超时且已完成的文件,加载和清理这两个动作由同一个线程完成,以防止冲突。

for (file <- logInfos) {
tasks += replayExecutor.submit(new Runnable {
override def run(): Unit = mergeApplicationListing(file)
})
}

在checkForLogs函数中,会首先检查文件是否有更新,已经扫描过的文件保存在一个以文件名为key的映射中fileToAppInfo,如果文件不在这个映射中,或者存在这个映射中但是文件大小变大了,则将此文件加入到加载列表中,随后进行解析。解析的过程是采用一个固定线程数的线程池replayExecutor对需要加载的文件进行解析,每解析完一个文件,会将此文件的信息更新至fileToAppInfo,这个过程在mergeApplicationListing函数中完成,另外pendingReplayTasksCount中保存了等待解析的文件数目,所有文件解析完成后,更新一下解析完成时间

private def replay(
eventLog: FileStatus,
appCompleted: Boolean,
bus: ReplayListenerBus,
eventsFilter: ReplayEventsFilter = SELECT_ALL_FILTER): ApplicationEventListener = {
val logPath = eventLog.getPath()
logInfo(s"Replaying log path: $logPath")
val logInput = EventLoggingListener.openEventLog(logPath, fs)
try {
val appListener = new ApplicationEventListener
bus.addListener(appListener)
bus.replay(logInput, logPath.toString, !appCompleted, eventsFilter)
appListener
} finally {
logInput.close()
}
}

在mergeApplicationListing函数中,主要通过执行reply函数将eventlog日志文件解析出来,在该函数中,首先将ApplicationEventListener监听器加入到ReplayListenerBus实例中,ReplayListenerBus主要通过调用该实例的replay函数从eventlog记录中解析event事件,每解析一个event,都会发通知到各监听器处理event,在这里通过监听者模式将日志解析与结果处理两个过程解耦开。执行完reply函数后,也就完成了一个eventlog文件的解析,如果解析成功,则将该eventlog的信息加入到fileToAppInfo,表明已经扫描过该文件

在cleanLogs函数中,会在log directory中删除已经任务执行完成且超时的文件。欢迎关注业余草:www.xttblog.com;CODE大全:www.codedq.net;爱分享:www.ndislwf.com

业余草教你解读Spark源码阅读之HistoryServer的更多相关文章

  1. emacs+ensime+sbt打造spark源码阅读环境

    欢迎转载,转载请注明出处,徽沪一郎. 概述 Scala越来越流行, Spark也愈来愈红火, 对spark的代码进行走读也成了一个很普遍的行为.不巧的是,当前java社区中很流行的ide如eclips ...

  2. Spark源码阅读之存储体系--存储体系概述与shuffle服务

    一.概述 根据<深入理解Spark:核心思想与源码分析>一书,结合最新的spark源代码master分支进行源码阅读,对新版本的代码加上自己的一些理解,如有错误,希望指出. 1.块管理器B ...

  3. win7+idea+maven搭建spark源码阅读环境

    1.参考. 利用IDEA工具编译Spark源码(1.60~2.20) https://blog.csdn.net/He11o_Liu/article/details/78739699 Maven编译打 ...

  4. spark源码阅读

    根据spark2.2的编译顺序来确定源码阅读顺序,只阅读核心的基本部分. 1.common目录 ①Tags②Sketch③Networking④Shuffle Streaming Service⑤Un ...

  5. spark源码阅读---Utils.getCallSite

    1 作用 当该方法在spark内部代码中调用时,会返回当前调用spark代码的用户类的名称,以及其所调用的spark方法.所谓用户类,就是我们这些用户使用spark api的类. 2 内部实现 2.1 ...

  6. spark源码阅读--SparkContext启动过程

    ##SparkContext启动过程 基于spark 2.1.0  scala 2.11.8 spark源码的体系结构实在是很庞大,从使用spark-submit脚本提交任务,到向yarn申请容器,启 ...

  7. spark源码阅读之network(2)

    在上节的解读中发现spark的源码中大量使用netty的buffer部分的api,该节将看到netty核心的一些api,比如channel: 在Netty里,Channel是通讯的载体(网络套接字或组 ...

  8. Spark源码阅读(1): Stage划分

    Spark中job由action动作生成,那么stage是如何划分的呢?一般的解答是根据宽窄依赖划分.那么我们深入源码看看吧 一个action 例如count,会在多次runJob中传递,最终会到一个 ...

  9. spark源码阅读之network(1)

    spark将在1.6中替换掉akka,而采用netty实现整个集群的rpc的框架,netty的内存管理和NIO支持将有效的提高spark集群的网络传输能力,为了看懂这块代码,在网上找了两本书看< ...

随机推荐

  1. Java 命令后台运行jar包

    nohup  java -jar XX.jar >temp.text & nohup 客户端关闭,后台继续运行 & 客户端关闭,后台停止运行 temp.text 是存控制台文件 ...

  2. ORACLE聚合函数细节

    select * from emp order by mgr; 概要 select count(1), --14 sum(1), --14 count(*), --14 count(distinct ...

  3. 安装lnmp集成环境

    具体配置看原文,不重新复述: 原文:https://lnmp.org/install.html 因为配置数据库主从,需要保持两台mysql数据库服务器的mysql版本号一致,所以又重新装了一次..重新 ...

  4. Promise (2) 基本方法

    "I'm Captain Jack Sparrow" 加勒比海盗5上映,为了表示对杰克船长的喜爱,昨天闪现了几次模仿船长的走路姿势(哈哈哈,简直妖娆). 为了周天能去看电影,要赶紧 ...

  5. 生成JSON数据--官方方法

    官方生成方法: 1)需要什么就给什么,要属性就给属性,要对象就给对象,要集合就给集合 2)添加都是使用put()方法 要求: 1.生成如下JSON数据: {"age":4,&quo ...

  6. ecshop的详细安装步骤

    从网上找个ecshop包,然后下载,解压,解压后的ecshop是不能直接用的,要更改几个目录的权限才能用. ecshop要放在www目录下,这样访问的话就可以直接 http://localhost/e ...

  7. 【javascript】Promise/A+ 规范简单实现 异步流程控制思想

    ——基于es6:Promise/A+ 规范简单实现 异步流程控制思想  前言: nodejs强大的异步处理能力使得它在服务器端大放异彩,基于它的应用不断的增加,但是异步随之带来的嵌套.难以理解的代码让 ...

  8. 常用SHELL命令

    1.查看版本号cat /proc/version; uname -a; uname -r 2.查看用户组cat /etc/group 3.查看当前用户组 groups 4.查看当前用户 whoami ...

  9. ASP.NET MVC开发学习过程中遇到的细节问题以及注意事项

    1.datagrid中JS函数传值问题: columns: { field: 'TypeName', title: '分类名称', width: 120, sortable: true, format ...

  10. 如何使用wait(), notify() and notifyAll() – Java

    Java多线程是个很复杂的问题,尤其在多线程在任何给定的时间访问共享资源需要更加注意.Java 5引入了一些类比如BlockingQueue 和Executors 类提供了易于使用的API,避免了一些 ...