Spark UI (基於Yarn) 分析與定製
這篇文章的主旨在於讓你瞭解Spark UI體系,並且能夠讓你有能力對UI進行一些定製化增強。在分析過程中,你也會深深的感受到Scala語言的魅力。
前言
有時候我們希望能對Spark UI進行一些定製化增強。並且我們希望儘可能不更改Spark的原始碼。為了達到此目標,我們會從如下三個方面進行闡述:
理解Spark UI的處理流程
現有Executors頁面分析
自己編寫一個HelloWord頁面
Spark UI 處理流程
Spark UI 在SparkContext 物件中進行初始化,對應的程式碼:
_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 }// Bind the UI before starting the task scheduler to communicate// the bound port to the cluster manager properly_ui.foreach(_.bind())
這裡做了兩個動作,
透過
SparkUI.createLiveUI
建立一個SparkUI例項_ui
透過
_ui.foreach(_.bind())
啟動jetty。bind 方法是繼承自WebUI,該類負責和真實的Jetty Server API打交道。
和傳統的Web服務不一樣,Spark並沒有使用什麼頁面模板引擎,而是自己定義了一套頁面體系。我們把這些物件分成兩類:
框架類,就是維護各個頁面關係,和Jetty API有關聯,負責管理的相關類。
頁面類,比如頁面的Tab,頁面渲染的內容等
框架類有:
SparkUI,該類繼承子WebUI,中樞類,負責啟動jetty,儲存頁面和URL Path之間的關係等。
WebUI
頁面類:
SparkUITab(繼承自WebUITab) ,就是首頁的標籤欄
WebUIPage,這個是具體的頁面。
SparkUI 負責整個Spark UI構建是,同時它是一切頁面的根物件。
對應的層級結構為:
SparkUI -> WebUITab -> WebUIPage
在SparkContext初始化的過程中,SparkUI會啟動一個Jetty。而建立起Jetty 和WebUIPage的橋樑是org.apache.spark.ui.WebUI
類,該類有個變數如下:
protected val handlers = ArrayBuffer[ServletContextHandler]()
這個org.eclipse.jetty.servlet.ServletContextHandler
是標準的jetty容器的handler,而
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
詳細看看。
目前spark 支援三種形態的http渲染結果:
text/json
text/html
text/plain
一般而言一個WebUIPage會對應兩個Handler,
val renderHandler = createServletHandler( pagePath, (request: HttpServletRequest) => page.render(request), securityManager, basePath) val renderJsonHandler = createServletHandler(pagePath.stripSuffix("/") + "/json", (request: HttpServletRequest) => page.renderJson(request), securityManager, basePath)
在頁面路徑上,html和json的區別就是html的url path 多加了一個"/json"字尾。 這裡可以看到,一般一個page最好實現
render
renderJson
兩個方法,以方便使用。
另外值得一提的是,上面的程式碼也展示了URL Path和對應的處理邏輯(Controller/Action)是如何關聯起來的。其實就是pagePath -> Page的render函式。
Executors頁面分析
我們以 Executors 顯示列表頁
為例子,來講述怎麼自定義開發一個Page。
首先你需要定義個Tab,也就是ExecutorsTab,如下:
private[ui] class ExecutorsTab(parent: SparkUI) extends SparkUITab(parent, "executors")
ExecutorsTab會作為一個標籤顯示在Spark首頁上。
接著定義一個ExecutorsPage
,作為標籤頁的呈現內容,並且透過
attachPage(new ExecutorsPage(this, threadDumpEnabled))
關聯上 ExecutorsTab 和 ExecutorsPage。
ExecutorsPage 的定義如下:
private[ui] class ExecutorsPage( parent: ExecutorsTab, threadDumpEnabled: Boolean) extends WebUIPage("")
實現ExecutorsPage.render
方法:
def render(request: HttpServletRequest): Seq[Node]
最後一步呼叫
SparkUIUtils.headerSparkPage("Executors (" + execInfo.size + ")", content, parent)
輸出設定頁面頭並且輸出content頁面內容。
這裡比較有意思的是,Spark 並沒有使用類似Freemarker或者Velocity等模板引擎,而是直接利用了Scala對html/xml的語法支援。類似這樣,寫起來也蠻爽的。
val execTable = <table class={UIUtils.TABLE_CLASS_STRIPED}> <thead> <th>Executor ID</th> <th>Address</th> <th>RDD Blocks</th> <th><span data-toggle="tooltip" title={ToolTips.STORAGE_MEMORY}>Storage Memory</span></th> <th>Disk Used</th> <th>Active Tasks</th>
如果想使用變數,使用{}
即可。
那最終這個Tag是怎麼新增到頁面上的呢?
如果你去翻看了原始碼,會比較心疼,他是在SparkUI的initialize
方法裡定義的:
def initialize() { attachTab(new JobsTab(this)) attachTab(stagesTab) attachTab(new StorageTab(this)) attachTab(new EnvironmentTab(this)) attachTab(new ExecutorsTab(this))
那我們新增的該怎麼辦?其實也很簡單啦,透過sparkContext獲取到 sparkUI物件,然後呼叫attachTab方法即可完成,具體如下:
sc.ui.getOrElse { throw new SparkException("Parent SparkUI to attach this tab to not found!")} .attachTab(new ExecutorsTab)
如果你是在spark-streaming裡,則簡單透過如下程式碼就能把你的頁面頁面新增進去:
ssc.start()new KKTab(ssc).attach()ssc.awaitTermination()
新增新的Tab可能會報錯,scala報的錯誤比較讓人困惑,可以試試加入下面依賴:
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> <version>9.3.6.v20151106</version></dependency>
實現新增一個HelloWord頁面
我們的例子很簡單,類似下面的圖:
無標題.png
按前文的描述,我們需要一個Tab頁,以及一個展示Tab對應內容的Page頁。其實就下面兩個類。
org.apache.spark.streaming.ui2.KKTab
:
package org.apache.spark.streaming.ui2 import org.apache.spark.streaming.StreamingContext import org.apache.spark.streaming.ui2.KKTab._ import org.apache.spark.ui.{SparkUI, SparkUITab} import org.apache.spark.{Logging, SparkException}/** * 1/1/16 WilliamZhu(allwefantasy@gmail.com) */class KKTab(val ssc: StreamingContext) extends SparkUITab(getSparkUI(ssc), "streaming2") with Logging { private val STATIC_RESOURCE_DIR = "org/apache/spark/streaming/ui/static" attachPage(new TTPage(this)) def attach() { getSparkUI(ssc).attachTab(this) getSparkUI(ssc).addStaticHandler(STATIC_RESOURCE_DIR, "/static/streaming") } def detach() { getSparkUI(ssc).detachTab(this) getSparkUI(ssc).removeStaticHandler("/static/streaming") } } private[spark] object KKTab { def getSparkUI(ssc: StreamingContext): SparkUI = { ssc.sc.ui.getOrElse { throw new SparkException("Parent SparkUI to attach this tab to not found!") } } }
org.apache.spark.streaming.ui2.TTPage
如下:
import org.apache.spark.Loggingimport org.apache.spark.ui.{UIUtils => SparkUIUtils, WebUIPage}import org.json4s.JsonAST.{JNothing, JValue}import scala.xml.Node/** * 1/1/16 WilliamZhu(allwefantasy@gmail.com) */private[spark] class TTPage(parent: KKTab) extends WebUIPage("") with Logging { override def render(request: HttpServletRequest): Seq[Node] = { val content = <p>TTPAGE</p> SparkUIUtils.headerSparkPage("TT", content, parent, Some(5000)) } override def renderJson(request: HttpServletRequest): JValue = JNothing }
記得新增上面提到的jetty依賴。
作者:祝威廉
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1834/viewspace-2818598/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spark:Yarn-client與Yarn-clusterSparkYarnclient
- spark 與 yarn 結合SparkYarn
- 18【線上日誌分析】之Spark on Yarn配置日誌Web UI(HistoryServer服務)SparkYarnWebUIServer
- 基於樹莓派的叢集實驗(一)--spark on yarn樹莓派SparkYarn
- Spark on Yarn 和Spark on MesosSparkYarn
- 12 Spark on YARNSparkYarn
- hive on spark on yarnHiveSparkYarn
- spark on yarn 的資源排程器設定.SparkYarn
- 原始碼分析:如何定製Semantic-UI原始碼UI
- 《Spark 3.0大資料分析與挖掘:基於機器學習》簡介Spark大資料機器學習
- Spark on Yarn 實踐SparkYarn
- Spark on Yarn 任務提交流程原始碼分析SparkYarn原始碼
- 基於 Spark 的資料分析實踐Spark
- 搭建spark on yarn 叢集SparkYarn
- Spark on Yarn 環境搭建SparkYarn
- CustomTkinter:基於Tkinter的現代且可定製的Python UI庫PythonUI
- Spark開發-Yarn cluster模式SparkYarn模式
- spark on yarn 資料插入mysqlSparkYarnMySql
- React-Admin 架構分析:Material-UI 定製React架構UI
- Spark on Yarn 部分一原理及使用SparkYarn
- Spark 原始碼系列(七)Spark on yarn 具體實現Spark原始碼Yarn
- 車載導航應用中基於Sketch UI主題定製方案的實現UI
- 【Spark篇】---Spark中yarn模式兩種提交任務方式SparkYarn模式
- 部署Spark2.2叢集(on Yarn模式)SparkYarn模式
- 構建與定製:唯品會PaaS基於Kubernetes的實踐
- Spark原始碼解析-Yarn部署流程(ApplicationMaster)Spark原始碼YarnAPPAST
- 基於vue的Element-ui定義自己的select元件VueUI元件
- 基於原始碼分析 Android View 繪製機制原始碼AndroidView
- YARN 核心原始碼分析Yarn原始碼
- 基於hadoop_yarn的資源隔離配置HadoopYarn
- 基於 RocketMQ Prometheus Exporter 打造定製化 DevOps 平臺MQPrometheusExportdev
- 實現B/S環境的UI定製UI
- 基於 ZooKeeper 搭建 Spark 高可用叢集Spark
- (課程)基於Spark的機器學習經驗Spark機器學習
- Python基於代理IP的挖掘與分析Python
- Yarn-cluster 與 Yarn-client的區別Yarnclient
- cdh版spark on yarn與idea直連操作sql遇到的一些問題SparkYarnIdeaSQL
- 基於lerna+yarn workspaces的monorepo專案實踐YarnMono