Spark2.1.0——內置Web框架詳解

来源:https://www.cnblogs.com/jiaan-geng/archive/2019/02/28/10313068.html
-Advertisement-
Play Games

任何系統都需要提供監控功能,否則在運行期間發生一些異常時,我們將會束手無策。也許有人說,可以增加日誌來解決這個問題。日誌只能解決你的程式邏輯在運行期的監控,進而發現Bug,以及提供對業務有幫助的調試信息。當你的JVM進程奔潰或者程式響應速度很慢時,這些日誌將毫無用處。好在JVM提供了jstat、js... ...


Spark2.1.0——內置Web框架詳解

  任何系統都需要提供監控功能,否則在運行期間發生一些異常時,我們將會束手無策。也許有人說,可以增加日誌來解決這個問題。日誌只能解決你的程式邏輯在運行期的監控,進而發現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內核設計的藝術 架構設計與實現》一書現已出版發行,圖書如圖:

Spark內核設計的藝術

 

紙質版售賣鏈接如下:

京東:https://item.jd.com/12302500.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在一次考試中,筆者因考試的電腦上沒有安裝操作Mysql資料庫的可視化工具而不知如何操作資料庫,所以在這裡可以提醒各位掌握 命令行來操作資料庫也是非常重要的。 筆者以慘痛的教訓來警惕大家。 進入正題: 使用命令行來操作資料庫分為以下幾個步驟: 前提: windows +R 運行 cmd.exe 步驟一 ...
  • 一、Kafka概述 1.Kafka是一個分散式流媒體平臺,它有三個關鍵功能: (1)發佈和訂閱記錄流,類似於消息隊列或企業消息傳遞系統; (2)以容錯的持久方式存儲記錄流; (3)記錄發送時處理流。 2.Kafka通常應用的兩大類應用 (1)構建在系統或應用程式之間的可靠獲取數據的實時流數據管道; ...
  • 筆記記錄自林曉斌(丁奇)老師的《MySQL實戰45講》 5) --深入淺出索引(下) 這次的筆記從一個簡單的查詢開始: 建表語句是這樣的 如果要執行 select * from T where k between 3 and 5這條語句,需要執行幾次搜索操作呢,會掃描多少行呢?由上面的建表及初始化語 ...
  • 禁用 1* select LAST_DATE,NEXT_DATE from dba_jobs where job=45SQL> begin 2 dbms_job.broken(45,true); 3 end; 4 / PL/SQL procedure successfully completed. ...
  • eclipse中寫入sql插入語句時,navicat中顯示的出現亂碼(???)。 在修改eclipse工作空間編碼、navicate中的資料庫編碼、mysql中my.ini中的配置之後還是出現亂碼。 然後把mysql、navicate全部卸載,下載新版本。 再重新配置mysql中,因為新建data里 ...
  • [20190227]簡單探究tab$的bojb#欄位.txt--//上午做了刪除tab$表,其對應索引i_tab1的恢復,我一直以為這個索引會很大,沒有想到在我的測試環境僅僅139個鍵值.--//查看/u01/app/oracle/product/11.2.0.4/dbhome_1/rdbms/ad ...
  • 大數據文摘出品 來源:Medium 編譯:李雷、橡樹_Hiangsug 文章解釋了轉型為數據科學家的原因,整理了數據科學家應該掌握的技能,著重介紹了從數據分析師轉型為數據科學家的具體方法。 如何從數據分析師華麗轉型,成為一名數據科學家?好比“把大象裝進冰箱”,成為“數據科學家”僅需簡單三步: 1. ...
  • 在學習菜鳥教程里的MySQL教程時,對左右連接的結果有點不解。 其中有如下兩個表: 執行右連接語句後: 得到的結果是: 我對這個結果感到很奇怪,右連接是會返回右表的所有行,不管有無匹配,但右表runoob_author明明有Google這個數據,為什麼會是NULL。 試著把所有列列印出來: 可以看到 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...