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一些相关配置参数,默认值,以及其作用。

参数 默认值 作用描述 spark.ui.enabled true 是否开启UI界面 spark.ui.port 4040(顺序探查空闲端口) UI界面的访问端口号 spark.ui.retainedJobs 1000 UI界面显示的Job个数 spark.ui.retailedStages 1000 UI界面上显示的Stage个数 spark.ui.timeline.tasks.maximum 1000 Stage页面显示的Tasks个数 spark.ui.killEnabled true 是否运行页面上kill任务 spark.ui.threadDumpsEnabled true Executors页面是否可以展示线程运行状况

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

阅读全文
0 0
原创粉丝点击