spark学习-56-源代码:SparkUI界面
来源:互联网 发布:加速器有mac 编辑:程序博客网 时间:2024/05/15 07:27
前言
在大型分布式系统中,采用事件监听机制是最常见的。为什么要使用事件监听机制?假如SparkUI采用Scala的函数调用方式,那么随着整个集群规模的增加,对函数的调用会越来越多,最终会受到Driver所在JVM的线程数量限制而影响监控数据的更新,甚至出现监控数据无法及时显示给用户的情况。由于函数调用多数情况下是同步调用,这就导致线程被阻塞,在分布式环境中,还可能因为网络问题,导致线程被长时间占用。将函数调用更换为发送事件,事件的处理是异步的,当前线程可以继续执行后续逻辑,线程池中的线程还可以被重用,这样整个系统的并发度会大大增加。发送的事件会存入缓存,由定时调度器取出后,分配给监听此事件的监听器对监控数据进行更新。
总体介绍
各个组件作简单介绍:DAGScheduler是主要的产生各类SparkListenerEvent的源头,它将各种SparkListenerEvent发送到listenerBus的事件队列中,listenerBus通过定时器将SparkListenerEvent事件匹配到具体的SparkListener,改变SparkListener中的统计监控数据,最终由SparkUI的界面展示。从图3-1中还可以看到Spark里定义了很多监听器SparkListener的实现,包括JobProgressListener、EnviromentListener、StorageListener、ExecutorsListener几种,它们的类继承体系如图
listener bus 可以理解为公交车,各种事件就是人,人到车站就下车,然后到各自的公司开始干活。
Spark UI界面实现方式
当Spark程序在运行时,会提供一个Web页面查看Application运行状态信息。是否开启UI界面由参数spark.ui.enabled(默认为true)来确定。下面列出Spark UI一些相关配置参数,默认值,以及其作用。
UI组件结构
这部分先讲UI界面的实现方式,UI界面的实例在本文最后一部分。如果对这部分中的某些概念不清楚,那么最好先把第二部分了解一下。
从下面UI界面的实例可以看出,不同的内容以Tab的形式展现在界面上,对应每一个Tab在下方显示具体内容。基本上Spark UI界面也是按这个层次关系实现的。
以SparkUI类为容器,各个Tab,如JobsTab, StagesTab, ExecutorsTab等镶嵌在SparkUI上,对应各个Tab,有页面内容实现类JobPage, StagePage, ExecutorsPage等页面。这些类的继承和包含关系如下图所示:
解释:
SparkUITab(继承自WebUITab) ,就是首页的标签栏
WebUIPage,这个是具体的页面。
其他的page都是主页面,点开按钮后显示的页面
上面就是Spark的UI主页,首先进来能看到的是Spark当前应用的job页面,在上面的导航栏:
1 代表job页面,在里面可以看到当前应用分析出来的所有任务,以及所有的excutors中action的执行时间。
2 代表stage页面,在里面可以看到应用的所有stage,stage是按照宽依赖来区分的,因此粒度上要比job更细一些
3 代表storage页面,我们所做的cache persist等操作,都会在这里看到,可以看出来应用目前使用了多少缓存
4 代表environment页面,里面展示了当前spark所依赖的环境,比如jdk,lib等等
5 代表executors页面,这里可以看到执行者申请使用的内存以及shuffle中input和output等数据
6 这是应用的名字,代码中如果使用setAppName,就会显示在这里
7 是job的主页面。
初始化过程
SparkUI类型的对象是UI界面的根对象,它是在SparkContext类中构造出来的。
/** 这里默认启动了SPark的UI界面 */ _ui = if (conf.getBoolean("spark.ui.enabled", true)) { // 调用了SparkUI.createLiveUI()-》create() Some(SparkUI.createLiveUI(this, _conf, listenerBus, _jobProgressListener, _env.securityManager, appName, startTime = startTime)) } else { // For tests, do not enable the UI None }
SparkUI.createLiveUI()方法调用了create()方法
def createLiveUI( sc: SparkContext, conf: SparkConf, listenerBus: SparkListenerBus, jobProgressListener: JobProgressListener, securityManager: SecurityManager, appName: String, startTime: Long): SparkUI = { create(Some(sc), conf, listenerBus, securityManager, appName, jobProgressListener = Some(jobProgressListener), startTime = startTime) }
/** * Create a new Spark UI. 创建一个新的Spark UI. * * @param sc optional SparkContext; this can be None when reconstituting a UI from event logs. * 可选SparkContext;当从事件日志重新构造一个UI时,这是不可能的。 * @param jobProgressListener if supplied, this JobProgressListener will be used; otherwise, the * web UI will create and register its own JobProgressListener. * 如果提供,这个JobProgressListener将被使用;否则,web UI将创建并注册自己的JobProgressListener。 * * 可以知道在create方法里除了JobProgressListener是外部传入的之外,又增加了一些SparkListener.例如 * 1.用于对JVM参数, Spark属性,Java系统属性,classpath等进行监控的EnvironmentListener; * 2.用于维护Executor的存储状态的StorageStatusListener; * 3.用于准备将Executor的信息展示在ExecutorsTab的ExecutorsListener; * 4.用于准备将Executor相关存储信息展示在BlockManagerUI的StorgeListener等。 * 5.最后创建的SparkUI,SparkUI服务默认是可以杀掉的,通过修改属性Spark.ui.killEnabled为false可以保证不被杀死 * 6.initialize方法会组织前端页面各个Tab和Page的展示和布局。 * */ private def create( sc: Option[SparkContext], conf: SparkConf, listenerBus: SparkListenerBus, securityManager: SecurityManager, appName: String, basePath: String = "", jobProgressListener: Option[JobProgressListener] = None, startTime: Long): SparkUI = { // 创建JobProgressListener val _jobProgressListener: JobProgressListener = jobProgressListener.getOrElse { val listener = new JobProgressListener(conf) listenerBus.addListener(listener) listener } // 一个SparkListener,它准备在environment选项卡上显示信息 val environmentListener = new EnvironmentListener // 存储状态 val storageStatusListener = new StorageStatusListener(conf) // executor监听器 在UI界面 ExecutorsTab显示的类 val executorsListener = new ExecutorsListener(storageStatusListener, conf) // BlockManagerUI按钮显示的信息 val storageListener = new StorageListener(storageStatusListener) // 一个SparkListener,它构造一个RDD操作的DAG。 val operationGraphListener = new RDDOperationGraphListener(conf) // 全部放在listenerBus中 listenerBus.addListener(environmentListener) listenerBus.addListener(storageStatusListener) listenerBus.addListener(executorsListener) listenerBus.addListener(storageListener) listenerBus.addListener(operationGraphListener) new SparkUI(sc, conf, securityManager, environmentListener, storageStatusListener, executorsListener, _jobProgressListener, storageListener, operationGraphListener, appName, basePath, startTime) }
这里在listenerBus中添加了environmentListener,storageStatusListener,executorsListener,storageListener,operationGraphListener到公交车中。
如果说listenerbus相当于公交车,那么这些监听器可以理解为车上的各种监控器,比如environmentListener相当于车上的摄像头,时刻监控着车内情况。油表监控,而event可以进一步理解为各种信息。油表多少信息等。
最后这个方法是 new SparkUI(),主要是执行了改类的初始化方法
/** Initialize all components of the server. * 初始化服务器的所有组件。 * */ def initialize() { val jobsTab = new JobsTab(this) attachTab(jobsTab) val stagesTab = new StagesTab(this) attachTab(stagesTab) attachTab(new StorageTab(this)) attachTab(new EnvironmentTab(this)) attachTab(new ExecutorsTab(this)) attachHandler(createStaticHandler(SparkUI.STATIC_RESOURCE_DIR, "/static")) attachHandler(createRedirectHandler("/", "/jobs/", basePath = basePath)) attachHandler(ApiRootResource.getServletHandler(this)) // These should be POST only, but, the YARN AM proxy won't proxy POSTs attachHandler(createRedirectHandler( "/jobs/job/kill", "/jobs/", jobsTab.handleKillRequest, httpMethods = Set("GET", "POST"))) attachHandler(createRedirectHandler( "/stages/stage/kill", "/stages/", stagesTab.handleKillRequest, httpMethods = Set("GET", "POST"))) } // 调用改类的初始化方法 initialize()
因为 SparkUI 继承 WebUI,我们看看WebUI都做了什么?
// WebUITab的tab页 是按钮对应的页面集合 protected val tabs = ArrayBuffer[WebUITab]()
这个存储了主界面对应的其他页面的tab按钮(对应JobsTab, StagesTab, ExecutorsTab等),点击后是每个具体的界面信息(对应JobPage, StagePage, ExecutorsPage等页面)
// 页面按钮对应的动作ServletContextHandler protected val handlers = ArrayBuffer[ServletContextHandler]() // 将页面和对应的动作对应起来 protected val pageToHandlers = new HashMap[WebUIPage, ArrayBuffer[ServletContextHandler]]
点击某个按钮总要有反应啊,按钮与反应得有一个对应关系。
pageToHandlers 则维护了WebUIPage到ServletContextHandler的对应关系。
这样,我们就得到了WebUIPage 和 Jetty Handler的对应关系了。一个Http请求就能够被对应的WebUIPage给承接。
从 MVC的角度而言,WebUIPage 更像是一个Controller(Action)。内部实现是WebUIPage被包括进了一个匿名的Servlet. 所以实际上Spark 实现了一个对Servlet非常Mini的封装。如果你感兴趣的话,可以到org.apache.spark.ui.JettyUtils 详细看看。
URL Path和对应的处理逻辑(Controller/Action)是如何关联起来的。其实就是pagePath -> Page的render函数。(这一点以后补充)
最后spark UI界面的启动是
/** Bind to the HTTP server behind this web interface. * 绑定到这个web接口后面的HTTP服务器。 * * SparkUI创建好了,需要调用父类的WebUI的bind方法,绑定host和端口,bind方法中主要的代码实现 * serverInfo = Some(startJettyServer(host, port, sslOptions, handlers, conf, name)) * */ def bind(): Unit = { assert(serverInfo.isEmpty, s"Attempted to bind $className more than once!") try { val host = Option(conf.getenv("SPARK_LOCAL_IP")).getOrElse("0.0.0.0") serverInfo = Some(startJettyServer(host, port, sslOptions, handlers, conf, name)) logInfo(s"Bound $className to $host, and started at $webUrl") } catch { case e: Exception => logError(s"Failed to bind $className", e) System.exit(1) } }
/** * Attempt to start a Jetty server bound to the supplied hostName:port using the given * context handlers. * * 尝试启动一个Jetty server绑定指定的主机名:端口使用给定的context handlers. * * If the desired port number is contended, continues incrementing ports until a free port is * found. Return the jetty Server object, the chosen port, and a mutable collection of handlers. * * 如果给定的port被占用了,那么就累加这个端口,一直到找到一个端口是空闲的,返回一个jetty Server对象,和一个处理程序的一个可变集合。 * */ def startJettyServer( hostName: String, port: Int, sslOptions: SSLOptions, handlers: Seq[ServletContextHandler], conf: SparkConf, serverName: String = ""): ServerInfo = { addFilters(handlers, conf) // Start the server first, with no connectors. val pool = new QueuedThreadPool // 设置服务名称 if (serverName.nonEmpty) { pool.setName(serverName) } // 设置为守护线程 pool.setDaemon(true) val server = new Server(pool) val errorHandler = new ErrorHandler() errorHandler.setShowStacks(true) errorHandler.setServer(server) server.addBean(errorHandler) val collection = new ContextHandlerCollection server.setHandler(collection) // Executor used to create daemon threads for the Jetty connectors. val serverExecutor = new ScheduledExecutorScheduler(s"$serverName-JettyScheduler", true) try { // 启动server server.start() // As each acceptor and each selector will use one thread, the number of threads should at // least be the number of acceptors and selectors plus 1. (See SPARK-13776) var minThreads = 1 def newConnector( connectionFactories: Array[ConnectionFactory], port: Int): (ServerConnector, Int) = { val connector = new ServerConnector( server, null, serverExecutor, null, -1, -1, connectionFactories: _*) connector.setPort(port) connector.start() // Currently we only use "SelectChannelConnector" // Limit the max acceptor number to 8 so that we don't waste a lot of threads connector.setAcceptQueueSize(math.min(connector.getAcceptors, 8)) connector.setHost(hostName) // The number of selectors always equals to the number of acceptors minThreads += connector.getAcceptors * 2 (connector, connector.getLocalPort()) } // If SSL is configured, create the secure connector first. val securePort = sslOptions.createJettySslContextFactory().map { factory => val securePort = sslOptions.port.getOrElse(if (port > 0) Utils.userPort(port, 400) else 0) val secureServerName = if (serverName.nonEmpty) s"$serverName (HTTPS)" else serverName val connectionFactories = AbstractConnectionFactory.getFactories(factory, new HttpConnectionFactory()) def sslConnect(currentPort: Int): (ServerConnector, Int) = { newConnector(connectionFactories, currentPort) } val (connector, boundPort) = Utils.startServiceOnPort[ServerConnector](securePort, sslConnect, conf, secureServerName) connector.setName(SPARK_CONNECTOR_NAME) server.addConnector(connector) boundPort } // Bind the HTTP port. def httpConnect(currentPort: Int): (ServerConnector, Int) = { newConnector(Array(new HttpConnectionFactory()), currentPort) } val (httpConnector, httpPort) = Utils.startServiceOnPort[ServerConnector](port, httpConnect, conf, serverName) // If SSL is configured, then configure redirection in the HTTP connector. securePort match { case Some(p) => httpConnector.setName(REDIRECT_CONNECTOR_NAME) val redirector = createRedirectHttpsHandler(p, "https") collection.addHandler(redirector) redirector.start() case None => httpConnector.setName(SPARK_CONNECTOR_NAME) } server.addConnector(httpConnector) // Add all the known handlers now that connectors are configured. handlers.foreach { h => h.setVirtualHosts(toVirtualHosts(SPARK_CONNECTOR_NAME)) val gzipHandler = new GzipHandler() gzipHandler.setHandler(h) collection.addHandler(gzipHandler) gzipHandler.start() } pool.setMaxThreads(math.max(pool.getMaxThreads, minThreads)) ServerInfo(server, httpPort, securePort, collection) } catch { case e: Exception => server.stop() if (serverExecutor.isStarted()) { serverExecutor.stop() } if (pool.isStarted()) { pool.stop() } throw e } }
参考文章:http://blog.csdn.net/dabokele/article/details/51843176
http://www.jianshu.com/p/8e4c38d0c44e
http://blog.csdn.net/beliefer/article/details/50720582
http://blog.csdn.net/dabokele/article/details/51843176
http://blog.sina.com.cn/s/blog_9ca9623b0102wepl.html
https://www.cnblogs.com/wuyida/p/6300237.html
http://blog.csdn.net/u013013024/article/details/73498508
https://www.cnblogs.com/xing901022/p/6445254.html
- spark学习-56-源代码:SparkUI界面
- 大数据Spark07_SparkShell、SparkUI界面、SparkHA、SparkShuffle、Spark内存管理
- spark技术分析—sparkUI
- spark报错:ERROR SparkUI: Failed to bind SparkUI,解决办法
- spark学习-60-源代码:ContextCleaner清理器
- Spark:java.net.BindException: Address already in use: Service 'SparkUI' failed after 16 retries!
- spark一个版本问题引发的血案(java.lang.NoSuchMethodError: org.apache.spark.ui.SparkUI.addStaticHandler)
- spark学习-55-源代码:SparkSession的的创建
- spark学习-61-源代码:ShutdownHookManager虚拟机关闭钩子管理器
- 数字水印学习系统之二 界面部分源代码
- service sparkUI port 4040
- Spark 源代码 SparkContext
- spark连接web界面
- Spark Web界面
- Spark UI界面原理
- Spark UI界面原理
- spark的UI界面
- spark学习 spark配置文件
- rm: cannot remove `libtoolT': No such file or directory 安装Apr出现的问题
- Linux MySQL安装教程
- Bjarne Stroustrup's FAQ(中文版)
- JVM--详解虚拟机字节码执行引擎之静态链接、动态链接与多态性实现机制
- Django2.0官方文档--概览
- spark学习-56-源代码:SparkUI界面
- 编译安装PHP,解决问题 Don't know how to define struct flock on this system, set --enable-opcache=no
- C# 单例设计模式
- 6-2 学生成绩链表处理(10 分) 本题要求实现两个函数,一个将输入的学生成绩组织成单向链表;另一个将成绩低于某分数线的学生结点从链表中删除。 函数接口定义: struct stud_node *
- Java线程池原码分析
- Python基础-函数-定义函数
- 穷举算法(鸡兔同笼)
- Mysql
- scanf();为什么要用取地址符