任何系统都需要提供监控功能, 否则在运行期间发生一些异常时, 我们将会束手无策. 也许有人说, 可以增加日志来解决这个问题. 日志只能解决你的程序逻辑在运行期的监控, 进而发现 Bug, 以及提供对业务有帮助的调试信息. 当你的 JVM 进程奔溃或者程序响应速度很慢时, 这些日志将毫无用处. 好在 JVM 提供了 jstat,jstack,jinfo,jmap,jhat 等工具帮助我们分析, 更有 VisualVM 的可视化界面以更加直观的方式对 JVM 运行期的状况进行监控. 此外, 像 Tomcat,Hadoop 等服务都提供了基于 Web 的监控页面, 用浏览器能访问具有样式及布局, 并提供丰富监控数据的页面无疑是一种简单, 高效的方式.
Spark 自然也提供了 Web 页面来浏览监控数据, 而且 Master,Worker,Driver 根据自身功能提供了不同内容的 Web 监控页面. 无论是 Master,Worker, 还是 Driver, 它们都使用了统一的 Web 框架 WebUI.Master,Worker 及 Driver 分别使用 MasterWebUI,WorkerWebUI 及 SparkUI 提供的 Web 界面服务, 后三者都继承自 WebUI, 并增加了个性化的功能. 此外, 在 Yarn 或 Mesos 模式下还有 WebUI 的另一个扩展实现 HistoryServer.HistoryServer 将会展现已经运行完成的应用程序信息. 本章以 SparkUI 为例, 并深入分析 WebUI 的框架体系.
SparkUI 概述
在大型分布式系统中, 采用事件监听机制是最常见的. 为什么要使用事件监听机制? 假如 Spark UI 采用 Scala 的函数调用方式, 那么随着整个集群规模的增加, 对函数的调用会越来越多, 最终会受到 Driver 所在 JVM 的线程数量限制而影响监控数据的更新, 甚至出现监控数据无法及时显示给用户的情况. 由于函数调用多数情况下是同步调用, 这就导致线程被阻塞, 在分布式环境中, 还可能因为网络问题, 导致线程被长时间占用. 将函数调用更换为发送事件, 事件的处理是异步的, 当前线程可以继续执行后续逻辑进而被快速释放. 线程池中的线程还可以被重用, 这样整个系统的并发度会大大增加. 发送的事件会存入缓存, 由定时调度器取出后, 分配给监听此事件的监听器对监控数据进行更新. Spark UI 就是这样的服务, 它的构成如图 1 所示.
图 1 SparkUI 的组成
图 1 展示了 SparkUI 中的各个组件, 这里对这些组件作简单介绍:
SparkListenerEvent 事件的来源: 包括 DAGScheduler,SparkContext,DriverEndpoint,BlockManagerMasterEndpoint 以及 LocalSchedulerBackend 等, 这些组件将会产生各种 SparkListenerEvent, 并发送到 listenerBus 的事件队列中. DriverEndpoint 是 Driver 在 Standalone 或 local-cluster 模式下与其他组件进行通信的组件, 在《Spark 内核设计的艺术》一书的第 9.9.2 节有详细介绍. BlockManagerMasterEndpoint 是 Driver 对分配给应用的所有 Executor 及其 BlockManager 进行统一管理的组件, 在《Spark 内核设计的艺术》一书的 6.8 节详细介绍. LocalSchedulerBackend 是 local 模式下的调度后端接口, 用于给任务分配资源或对任务的状态进行更新, 在《Spark 内核设计的艺术》一书的 7.8.2 节详细介绍.
事件总线 listenerBus. 根据 3.3 节对事件总线的介绍, 我们知道 listenerBus 通过定时器将 SparkListenerEvent 事件匹配到具体的 SparkListener, 进而改变各个 SparkListener 中的统计监控数据.
Spark UI 的界面. 各个 SparkListener 内的统计监控数据将会被各种标签页和具体页面展示到 Web 界面. 标签页有 StagesTab,JobsTab,ExecutorsTab,EnvironmentTab 以及 StorageTab. 每个标签页中包含若干个页面, 例如 StagesTab 标签页中包含了 AllStagesPage,StagePage 及 PoolPage 三个页面.
控制台的展示. 细心的读者会发现图 1 中还有 SparkStatusTracker(Spark 状态跟踪器)和 ConsoleProgressBar(控制台进度条)两个组件. SparkStatusTracker 负责对 Job 和 Stage 的监控, 其实际也是使用了 JobProgressListener 中的监控数据, 并额外进行了一些加工. ConsoleProgressBar 负责将 SparkStatusTracker 提供的数据打印到控制台上. 从最终展现的角度来看, SparkStatusTracker 和 ConsoleProgressBar 不应该属于 SparkUI 的组成部分, 但是由于其实现与 JobProgressListener 密切相关, 所以将它们也放在了 SparkUI 的内容中.
WebUI 框架体系
Spark UI 构建在 WebUI 的框架体系之上, 因此应当首先了解 WebUI.WebUI 定义了一种 Web 界面展现的框架, 并提供返回 JSON 格式数据的 Web 服务. WebUI 用于展示一组标签页, WebUITab 定义了标签页的规范. 每个标签页中包含着一组页面, WebUIPage 定义了页面的规范. 我们将首先了解 WebUIPage 和 WebUITab, 最后从整体来看 WebUI.
WebUIPage 的定义
任何的 Web 界面往往由多个页面组成, 每个页面都将提供不同的内容展示. WebUIPage 是 WebUI 框架体系的页节点, 定义了所有页面应当遵循的规范. 抽象类 WebUIPage 的定义见代码清单 1.
代码清单 1 WebUIPage 的定义
- private[spark] abstract class WebUIPage(var prefix: String) {
- def render(request: HttpServletRequest): Seq[Node]
- def renderJson(request: HttpServletRequest): JValue = JNothing
- }
WebUIPage 定义了两个方法.
render: 渲染页面;
renderJson: 生成 JSON.
WebUIPage 在 WebUI 框架体系中的上一级节点 (也可以称为父亲) 可以是 WebUI 或者 WebUITab, 其成员属性 prefix 将与上级节点的路径一起构成当前 WebUIPage 的访问路径.
WebUITab 的定义
有时候 Web 界面需要将多个页面作为一组内容放置在一起, 这时候标签页是常见的展现形式. 标签页 WebUITab 定义了所有标签页的规范, 并用于展现一组 WebUIPage. 抽象类 WebUITab 的定义见代码清单 2.
代码清单 2 WebUITab 的定义
- private[spark] abstract class WebUITab(parent: WebUI, val prefix: String) {
- val pages = ArrayBuffer[WebUIPage]()
- val name = prefix.capitalize
- def attachPage(page: WebUIPage) {
- page.prefix = (prefix + "/" + page.prefix).stripSuffix("/")
- pages += page
- }
- def headerTabs: Seq[WebUITab] = parent.getTabs
- def basePath: String = parent.getBasePath
- }
根据代码清单 2, 可以看到 WebUITab 有四个成员属性:
parent: 上一级节点, 即父亲. WebUITab 的父亲只能是 WebUI.
prefix: 当前 WebUITab 的前缀. prefix 将与上级节点的路径一起构成当前 WebUITab 的访问路径.
pages: 当前 WebUITab 所包含的 WebUIPage 的缓冲数组.
name: 当前 WebUITab 的名称. name 实际是对 prefix 的首字母转换成大写字母后取得.
此外, WebUITab 还有三个成员方法, 下面介绍它们的作用:
attachPage: 首先将当前 WebUITab 的前缀与 WebUIPage 的前缀拼接, 作为 WebUIPage 的访问路径. 然后向 pages 中添加 WebUIPage.
headerTabs: 获取父亲 WebUI 中的所有 WebUITab. 此方法实际通过调用父亲 WebUI 的 getTabs 方法实现, getTabs 方法请参阅下一小节 --WebUI 的定义.
basePath: 获取父亲 WebUI 的基本路径. 此方法实际通过调用父亲 WebUI 的 getBasePath 方法实现, getBasePath 方法请参阅下一小节 --WebUI 的定义..
WebUI 的定义
WebUI 是 Spark 实现的用于提供 Web 界面展现的框架, 凡是需要页面展现的地方都可以继承它来完成. WebUI 定义了 WebUI 框架体系的规范. 为便于理解, 首先明确 WebUI 中各个成员属性的含义:
securityManager:SparkEnv 中创建的安全管理器 SecurityManager,5.2 节对 SecurityManager 有详细介绍.
sslOptions: 使用 SecurityManager 获取 spark.ssl.ui 属性指定的 WebUI 的 SSL(Secure Sockets Layer 安全套接层)选项.
port:WebUI 对外服务的端口. 可以使用 spark.ui.port 属性进行配置.
conf: 即 SparkConf.
basePath:WebUI 的基本路径. basePath 默认为空字符串.
name:WebUI 的名称. Spark UI 的 name 为 SparkUI.
tabs:WebUITab 的缓冲数组.
handlers:ServletContextHandler 的缓冲数组. ServletContextHandler 是 Jetty 提供的 API, 负责对 ServletContext 进行处理. ServletContextHandler 的使用及 Jetty 的更多内容可以参阅附录 C.
pageToHandlers:WebUIPage 与 ServletContextHandler 缓冲数组之间的映射关系. 由于 WebUIPage 的两个方法 render 和 renderJson 分别需要由一个对应的 ServletContextHandler 处理. 所以一个 WebUIPage 对应两个 ServletContextHandler.
serverInfo: 用于缓存 ServerInfo, 即 WebUI 的 Jetty 服务器信息.
publicHostName: 当前 WebUI 的 Jetty 服务的主机名. 优先采用系统环境变量 SPARK_PUBLIC_DNS 指定的主机名, 否则采用 spark.driver.host 属性指定的 host, 在没有前两个配置的时候, 将默认使用工具类 Utils 的 localHostName 方法 (详见附录 A) 返回的主机名.
className: 过滤了 $ 符号的当前类的简单名称. className 是通过 Utils 的 getFormattedClassName 方法得到的. getFormattedClassName 方法的实现请看附录 A.
了解了 WebUI 的成员属性, 现在就可以理解其提供的各个方法了. WebUI 提供的方法有:
getBasePath: 获取 basePath.
getTabs: 获取 tabs 中的所有 WebUITab, 并以 Scala 的序列返回.
getHandlers: 获取 handlers 中的所有 ServletContextHandler, 并以 Scala 的序列返回.
getSecurityManager: 获取 securityManager.
attachHandler: 给 handlers 缓存数组中添加 ServletContextHandler, 并且将此 ServletContextHandler 通过 ServerInfo 的 addHandler 方法添加到 Jetty 服务器中. attachHandler 的实现见代码清单 3.ServerInfo 的 addHandler 方法的请参阅附录 C.
代码清单 3 attachHandler 的实现
- def attachHandler(handler: ServletContextHandler) {
- handlers += handler
- serverInfo.foreach(_.addHandler(handler))
- }
detachHandler: 从 handlers 缓存数组中移除 ServletContextHandler, 并且将此 ServletContextHandler 通过 ServerInfo 的 removeHandler 方法从 Jetty 服务器中移除. detachHandler 的实现见代码清单 4.ServerInfo 的 removeHandler 方法的请参阅附录 C.
代码清单 4 detachHandler 的实现
- def detachHandler(handler: ServletContextHandler) {
- handlers -= handler
- serverInfo.foreach(_.removeHandler(handler))
- }
attachPage: 首先调用工具类 JettyUtils[1]的 createServletHandler 方法给 WebUIPage 创建与 render 和 renderJson 两个方法分别关联的 ServletContextHandler, 然后通过 attachHandler 方法添加到 handlers 缓存数组与 Jetty 服务器中, 最后把 WebUIPage 与这两个 ServletContextHandler 的映射关系更新到 pageToHandlers 中. attachPage 的实现见代码清单 5.
代码清单 5 attachPage 的实现
- def attachPage(page: WebUIPage) {
- val pagePath = "/" + page.prefix
- val renderHandler = createServletHandler(pagePath,
- (request: HttpServletRequest) => page.render(request), securityManager, conf, basePath)
- val renderJsonHandler = createServletHandler(pagePath.stripSuffix("/") + "/json",
- (request: HttpServletRequest) => page.renderJson(request), securityManager, conf, basePath)
- attachHandler(renderHandler)
- attachHandler(renderJsonHandler)
- val handlers = pageToHandlers.getOrElseUpdate(page, ArrayBuffer[ServletContextHandler]())
- handlers += renderHandler
- }
detachPage: 作用与 attachPage 相反. detachPage 的实现见代码清单 6.
代码清单 6 detachPage 的实现
- def detachPage(page: WebUIPage) {
- pageToHandlers.remove(page).foreach(_.foreach(detachHandler))
- }
attachTab: 首先向 tabs 中添加 WebUITab, 然后给 WebUITab 中的每个 WebUIPage 施加 attachPage 方法. attachTab 的实现见代码清单 7.
代码清单 7 attachTab 的实现
- def attachTab(tab: WebUITab) {
- tab.pages.foreach(attachPage)
- tabs += tab
- }
detachTab: 作用与 attachTab 相反. detachTab 的实现见代码清单 8.
代码清单 8 detachTab 的实现
- def detachTab(tab: WebUITab) {
- tab.pages.foreach(detachPage)
- tabs -= tab
- }
addStaticHandler: 首先调用工具类 JettyUtils 的 createStaticHandler 方法创建静态文件服务的 ServletContextHandler, 然后施加 attachHandler 方法. addStaticHandler 的实现见代码清单 9.JettyUtils 的 createStaticHandler 方法的实现见附录 C.
代码清单 9 addStaticHandler 的实现
- def addStaticHandler(resourceBase: String, path: String): Unit = {
- attachHandler(JettyUtils.createStaticHandler(resourceBase, path))
- }
removeStaticHandler: 作用与 addStaticHandler 相反. removeStaticHandler 的实现见代码清单 10.
代码清单 10 removeStaticHandler 的实现
- def removeStaticHandler(path: String): Unit = {
- handlers.find(_.getContextPath() == path).foreach(detachHandler)
- }
initialize: 用于初始化 WebUI 服务中的所有组件. WebUI 中此方法未实现, 需要子类实现.
bind: 启动与 WebUI 绑定的 Jetty 服务. bind 方法的实现见代码清单 11.
代码清单 11 bind 的实现
- def bind() {
- assert(!serverInfo.isDefined, 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)
- }
- }
webUrl: 获取 WebUI 的 Web 界面的 URL.webUrl 的实现如下:
def webUrl: String = shttp://$publicHostName:$boundPort
boundPort: 获取 WebUI 的 Jetty 服务的端口. boundPort 的实现如下:
def boundPort: Int = serverInfo.map(_.boundPort).getOrElse(-1)
stop: 停止 WebUI. 实际是停止 WebUI 底层的 Jetty 服务. stop 方法的实现见代码清单 12.
代码清单 12 stop 方法的实现
- def stop() {
- assert(serverInfo.isDefined,
- s"Attempted to stop $className before binding to a server!")
- serverInfo.get.stop()
- }
创建 SparkUI
在 SparkContext 的初始化过程中, 会创建 SparkUI. 有了对 WebUI 的总体认识, 现在是时候了解 SparkContext 是如何构造 SparkUI 的了. SparkUI 是 WebUI 框架的使用范例, 了解了 SparkUI 的创建过程, 读者对 MasterWebUI,WorkerWebUI 及 HistoryServer 的创建过程也必然了然于心. 创建 SparkUI 的代码如下:
- _statusTracker = new SparkStatusTracker(this)
- _progressBar =
- if (_conf.getBoolean("spark.ui.showConsoleProgress", true) && !log.isInfoEnabled) {
- Some(new ConsoleProgressBar(this))
- } else {
- None
- }
- _ui =
- if (conf.getBoolean("spark.ui.enabled", true)) {
- Some(SparkUI.createLiveUI(this, _conf, listenerBus, _jobProgressListener,
- _env.securityManager, appName, startTime = startTime))
- } else {
- // For tests, do not enable the UI
- None
- }
- _ui.foreach(_.bind())
这段代码的执行步骤如下.
1) 创建 Spark 状态跟踪器 SparkStatusTracker.
2) 创建 ConsoleProgressBar. 可以配置 spark.ui.showConsoleProgress 属性为 false 取消对 ConsoleProgressBar 的创建, 此属性默认为 true.
3) 调用 SparkUI 的 createLiveUI 方法创建 SparkUI.
4) 给 SparkUI 绑定端口. SparkUI 继承自 WebUI, 因此调用了代码清单 4-12 中 WebUI 的 bind 方法启动 SparkUI 底层的 Jetty 服务.
上述步骤中, 第 1),2),4)步都很简单, 所以着重来分析第 3)步. SparkUI 的 createLiveUI 的实现如下.
- 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)
- }
可以看到 SparkUI 的 createLiveUI 方法中调用了 create 方法. create 的实现如下.
- private def create(
- sc: Option[SparkContext],
- conf: SparkConf,
- listenerBus: SparkListenerBus,
- securityManager: SecurityManager,
- appName: String,
- basePath: String = "",
- jobProgressListener: Option[JobProgressListener] = None,
- startTime: Long): SparkUI = {
- val _jobProgressListener: JobProgressListener = jobProgressListener.getOrElse {
- val listener = new JobProgressListener(conf)
- listenerBus.addListener(listener)
- listener
- }
- val environmentListener = new EnvironmentListener
- val storageStatusListener = new StorageStatusListener(conf)
- val executorsListener = new ExecutorsListener(storageStatusListener, conf)
- val storageListener = new StorageListener(storageStatusListener)
- val operationGraphListener = new RDDOperationGraphListener(conf)
- 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)
- }
可以看到 create 方法里除了 JobProgressListener 是外部传入的之外, 又增加了一些 SparkListener, 例如用于对 JVM 参数, Spark 属性, Java 系统属性, classpath 等进行监控的 EnvironmentListener; 用于维护 Executor 的存储状态的 StorageStatusListener; 用于准备将 Executor 的信息展示在 ExecutorsTab 的 ExecutorsListener; 用于准备将 Executor 相关存储信息展示在 BlockManagerUI 的 StorageListener; 用于构建 RDD 的 DAG(有向无关图)的 RDDOperationGraphListener 等. 这 5 个 SparkListener 的实现添加到 listenerBus 的监听器列表中. 最后使用 SparkUI 的构造器创建 SparkUI.
SparkUI 的初始化
调用 SparkUI 的构造器创建 SparkUI, 实际也是对 SparkUI 的初始化过程. 在介绍初始化之前, 先来看看 SparkUI 中的两个成员属性.
killEnabled: 标记当前 SparkUI 能否提供杀死 Stage 或者 Job 的链接.
appId: 当前应用的 ID.
SparkUI 的构造过程中会执行 initialize 方法, 其实现见代码清单 13.
代码清单 13 SparkUI 的初始化
- 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()
根据代码清单 13,SparkUI 的初始化步骤如下.
1) 构建页面布局并给每个 WebUITab 中的所有 WebUIPage 创建对应的 ServletContextHandler. 这一步使用了代码清单 4-8 中展示的 attachTab 方法.
2) 调用 JettyUtils 的 createStaticHandler 方法创建对静态目录 org/apache/spark/ui/static 提供文件服务的 ServletContextHandler, 并使用 attachHandler 方法追加到 SparkUI 的服务中.
3) 调用 JettyUtils 的 createRedirectHandler 方法创建几个将用户对源路径的请求重定向到目标路径的 ServletContextHandler. 例如, 将用户对根路径 "/" 的请求重定向到目标路径 "/jobs/" 的 ServletContextHandler.
SparkUI 的页面布局与展示
SparkUI 究竟是如何实现页面布局及展示的? 由于所有标签页都继承了 SparkUITab, 所以我们先来看看 SparkUITab 的实现:
- private[spark] abstract class SparkUITab(parent: SparkUI, prefix: String)
- extends WebUITab(parent, prefix) {
- def appName: String = parent.getAppName
- }
根据上述代码, 我们知道 SparkUITab 继承了 WebUITab, 并在实现中增加了一个用于获取当前应用名称的方法 appName.EnvironmentTab 是用于展示 JVM,Spark 属性, 系统属性, 类路径等相关信息的标签页, 由于其实现简单且能说明问题, 所以本节挑选 EnvironmentTab 作为示例解答本节一开始提出的问题.
EnvironmentTab 的实现见代码清单 14.
代码清单 14 EnvironmentTab 的实现
- private[ui] class EnvironmentTab(parent: SparkUI) extends SparkUITab(parent, "environment") {
- val listener = parent.environmentListener
- attachPage(new EnvironmentPage(this))
- }
根据代码清单 14, 我们知道 EnvironmentTab 引用了 SparkUI 的 environmentListener(类型为 EnvironmentListener), 并且包含 EnvironmentPage 这个页面. EnvironmentTab 通过调用 attachPage 方法将 EnvironmentPage 与 Jetty 服务关联起来. 根据代码清单 5 中 attachPage 的实现, 创建的 renderHandler 将采用偏函数(request: HttpServletRequest) => page.render(request) 处理请求, 因而会调用 EnvironmentPage 的 render 方法. EnvironmentPage 的 render 方法将会渲染页面元素. EnvironmentPage 的实现见代码清单 15.
代码清单 15 EnvironmentPage 的实现
- private[ui] class EnvironmentPage(parent: EnvironmentTab) extends WebUIPage("") {
- private val listener = parent.listener
- private def removePass(kv: (String, String)): (String, String) = {
- if (kv._1.toLowerCase.contains("password") || kv._1.toLowerCase.contains("secret")) {
- (kv._1, "******")
- } else kv
- }
- def render(request: HttpServletRequest): Seq[Node] = {
- // 调用 UIUtils 的 listingTable 方法生成 JVM 运行时信息, Spark 属性信息, 系统属性信息, 类路径信息的表格
- val runtimeInformationTable = UIUtils.listingTable(
- propertyHeader, jvmRow, listener.jvmInformation, fixedWidth = true)
- val sparkPropertiesTable = UIUtils.listingTable(
- propertyHeader, propertyRow, listener.sparkProperties.map(removePass), fixedWidth = true)
- val systemPropertiesTable = UIUtils.listingTable(
- propertyHeader, propertyRow, listener.systemProperties, fixedWidth = true)
- val classpathEntriesTable = UIUtils.listingTable(
- classPathHeaders, classPathRow, listener.classpathEntries, fixedWidth = true)
- val content =
- <span>
- <h4>Runtime Information</h4> {runtimeInformationTable}
- <h4>Spark Properties</h4> {sparkPropertiesTable}
- <h4>System Properties</h4> {systemPropertiesTable}
- <h4>Classpath Entries</h4> {classpathEntriesTable}
- </span>
- // 调用 UIUtils 的 headerSparkPage 方法封装好 CSS,JS,header 及页面布局等
- UIUtils.headerSparkPage("Environment", content, parent)
- }
- // 定义 JVM 运行时信息, Spark 属性信息, 系统属性信息的表格头部 propertyHeader 和类路径信息的表格头部
- // classPathHeaders
- private def propertyHeader = Seq("Name", "Value")
- private def classPathHeaders = Seq("Resource", "Source")
- // 定义 JVM 运行时信息的表格中每行数据的生成方法 jvmRow
- private def jvmRow(kv: (String, String)) = <tr><td>{kv._1}</td><td>{kv._2}</td></tr>
- private def propertyRow(kv: (String, String)) = <tr><td>{kv._1}</td><td>{kv._2}</td></tr>
- private def classPathRow(data: (String, String)) = <tr><td>{data._1}</td><td>{data._2}</td></tr>
- }
根据代码清单 15,EnvironmentPage 的 render 方法利用从父节点 EnvironmentTab 中得到的 EnvironmentListener 中的统计监控数据生成 JVM 运行时, Spark 属性, 系统属性以及类路径等状态的摘要信息. 以 JVM 运行时为例, 页面渲染的步骤如下:
1) 定义 JVM 运行时信息, Spark 属性信息, 系统属性信息的表格头部 propertyHeader 和类路径信息的表格头部 classPathHeaders.
2) 定义 JVM 运行时信息的表格中每行数据的生成方法 jvmRow.
3) 调用 UIUtils 的 listingTable 方法生成 JVM 运行时信息, Spark 属性信息, 系统属性信息, 类路径信息的表格.
4) 调用 UIUtils 的 headerSparkPage 方法封装好 CSS,JS,header 及页面布局等.
UIUtils 工具类的实现细节留给感兴趣的读者自行查阅, 本文不多赘述.
[1]本节内容用到 JettyUtils 中的很多方法, 读者可以在附录 C 中找到相应的实现与说明.
关于《Spark 内核设计的艺术 架构设计与实现》
经过近一年的准备, 基于 Spark2.1.0 版本的《Spark 内核设计的艺术 架构设计与实现》一书现已出版发行, 图书如图:
纸质版售卖链接如下:
京东: https://item.jd.com/12302500.html
来源: https://www.cnblogs.com/jiaan-geng/p/10313068.html