结合html做界面_Spark UI界面实现原理
當(dāng)Spark程序在運(yùn)行時(shí),會(huì)提供一個(gè)Web頁(yè)面查看Application運(yùn)行狀態(tài)信息。是否開啟UI界面由參數(shù)spark.ui.enabled(默認(rèn)為true)來確定。下面列出Spark UI一些相關(guān)配置參數(shù),默認(rèn)值,以及其作用。
本文接下來分成兩個(gè)部分,第一部分基于Spark-1.6.0的源碼,結(jié)合第二部分的圖片內(nèi)容來描述UI界面在Spark中的實(shí)現(xiàn)方式。第二部分以實(shí)例展示Spark UI界面顯示的內(nèi)容。
一、Spark UI界面實(shí)現(xiàn)方式
1、UI組件結(jié)構(gòu)
這部分先講UI界面的實(shí)現(xiàn)方式,UI界面的實(shí)例在本文最后一部分。如果對(duì)這部分中的某些概念不清楚,那么最好先把第二部分了解一下。
從下面UI界面的實(shí)例可以看出,不同的內(nèi)容以Tab的形式展現(xiàn)在界面上,對(duì)應(yīng)每一個(gè)Tab在下方顯示具體內(nèi)容。基本上Spark UI界面也是按這個(gè)層次關(guān)系實(shí)現(xiàn)的。
以SparkUI類為容器,各個(gè)Tab,如JobsTab, StagesTab, ExecutorsTab等鑲嵌在SparkUI上,對(duì)應(yīng)各個(gè)Tab,有頁(yè)面內(nèi)容實(shí)現(xiàn)類JobPage, StagePage, ExecutorsPage等頁(yè)面。這些類的繼承和包含關(guān)系如下圖所示:
2、初始化過程
從上面可以看出,SparkUI類型的對(duì)象是UI界面的根對(duì)象,它是在SparkContext類中構(gòu)造出來的。
private var _ui: Option[SparkUI] = None //定義_ui = //SparkUI對(duì)象的生成
?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()) ?//啟動(dòng)jetty。bind方法繼承自WebUI,該類負(fù)責(zé)和真實(shí)的Jetty Server API打交道
上面這段代碼中可以看到SparkUI對(duì)象的生成過程,結(jié)合上面的類結(jié)構(gòu)圖,可以看到bind方法繼承自WebUI類,進(jìn)入WebUI類中,
?protected val handlers = ArrayBuffer[ServletContextHandler]() // 這個(gè)對(duì)象在下面bind方法中會(huì)使用到。?protected val pageToHandlers = new HashMap[WebUIPage, ArrayBuffer[ServletContextHandler]] // 將page綁定到handlers上
?/** 將Http Server綁定到這個(gè)Web頁(yè)面 */
?def bind() {
? ?assert(!serverInfo.isDefined, "Attempted to bind %s more than once!".format(className))
? ?try {
? ? ?serverInfo = Some(startJettyServer("0.0.0.0", port, handlers, conf, name))
? ? ?logInfo("Started %s at http://%s:%d".format(className, publicHostName, boundPort))
? ?} catch {
? ? ?case e: Exception =>
? ? ? ?logError("Failed to bind %s".format(className), e)
? ? ? ?System.exit(1)
? ?}
?}
上面代碼中handlers對(duì)象維持了WebUIPage和Jetty之間的關(guān)系,org.eclipse.jetty.servlet.ServletContextHandler是標(biāo)準(zhǔn)jetty容器的handler。而對(duì)象pageToHandlers維持了WebUIPage到ServletContextHandler的對(duì)應(yīng)關(guān)系。
各Tab頁(yè)以及該頁(yè)內(nèi)容的實(shí)現(xiàn),基本上大同小異。接下來以AllJobsPage頁(yè)面為例仔細(xì)梳理頁(yè)面展示的過程。
3、SparkUI中Tab的綁定
從上面的類結(jié)構(gòu)圖中看到WebUIPage提供了兩個(gè)重要的方法,render和renderJson用于相應(yīng)頁(yè)面請(qǐng)求,在WebUIPage的實(shí)現(xiàn)類中,具體實(shí)現(xiàn)了這兩個(gè)方法。在SparkContext中構(gòu)造出SparkUI的實(shí)例后,會(huì)執(zhí)行SparkUI#initialize方法進(jìn)行初始化。如下面代碼中,調(diào)用SparkUI從WebUI繼承的attacheTab方法,將各Tab頁(yè)面綁定到UI上。
def initialize() {? ?attachTab(new JobsTab(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))
? ?// This should be POST only, but, the YARN AM proxy won't proxy POSTs
? ?attachHandler(createRedirectHandler(
? ? ?"/stages/stage/kill", "/stages/", stagesTab.handleKillRequest,
? ? ?httpMethods = Set("GET", "POST")))
?}
4、頁(yè)面內(nèi)容綁定到Tab
在上一節(jié)中,JobsTab標(biāo)簽綁定到SparkUI上之后,在JobsTab上綁定了AllJobsPage和JobPage類。AllJobsPage頁(yè)面即訪問SparkUI頁(yè)面時(shí)列舉出所有Job的那個(gè)頁(yè)面,JobPage頁(yè)面則是點(diǎn)擊單個(gè)Job時(shí)跳轉(zhuǎn)的頁(yè)面。通過調(diào)用JobsTab從WebUITab繼承的attachPage方法與JobsTab進(jìn)行綁定。
private[ui] class JobsTab(parent: SparkUI) extends SparkUITab(parent, "jobs") {?val sc = parent.sc
?val killEnabled = parent.killEnabled
?val jobProgresslistener = parent.jobProgressListener
?val executorListener = parent.executorsListener
?val operationGraphListener = parent.operationGraphListener
?def isFairScheduler: Boolean =
? ?jobProgresslistener.schedulingMode.exists(_ == SchedulingMode.FAIR)
?attachPage(new AllJobsPage(this))
?attachPage(new JobPage(this))
}
5、頁(yè)面內(nèi)容的展示
知道了AllJobsPage頁(yè)面如何綁定到SparkUI界面后,接下來分析這個(gè)頁(yè)面的內(nèi)容是如何顯示的。進(jìn)入AllJobsPage類,主要觀察render方法。在頁(yè)面展示上Spark直接利用了Scala對(duì)html/xml的語法支持,將頁(yè)面的Html代碼嵌入Scala程序中。具體的頁(yè)面生成過程可以查看下面源碼中的注釋。這里可以結(jié)合第二部分的實(shí)例進(jìn)行查看。
?def render(request: HttpServletRequest): Seq[Node] = {? ?val listener = parent.jobProgresslistener //獲取jobProgresslistener對(duì)象,頁(yè)面展示的數(shù)據(jù)都是從這里讀取
? ?listener.synchronized {
? ? ?val startTime = listener.startTime // 獲取application的開始時(shí)間,默認(rèn)值為-1L
? ? ?val endTime = listener.endTime // 獲取application的結(jié)束時(shí)間,默認(rèn)值為-1L
? ? ?val activeJobs = listener.activeJobs.values.toSeq // 獲取當(dāng)前application中處于active狀態(tài)的job
? ? ?val completedJobs = listener.completedJobs.reverse.toSeq // 獲取當(dāng)前application中完成狀態(tài)的job
? ? ?val failedJobs = listener.failedJobs.reverse.toSeq ?// 獲取當(dāng)前application中失敗狀態(tài)的job
? ? ?val activeJobsTable =
? ? ? ?jobsTable(activeJobs.sortBy(_.submissionTime.getOrElse(-1L)).reverse)
? ? ?val completedJobsTable =
? ? ? ?jobsTable(completedJobs.sortBy(_.completionTime.getOrElse(-1L)).reverse)
? ? ?val failedJobsTable =
? ? ? ?jobsTable(failedJobs.sortBy(_.completionTime.getOrElse(-1L)).reverse)
? ? ?val shouldShowActiveJobs = activeJobs.nonEmpty
? ? ?val shouldShowCompletedJobs = completedJobs.nonEmpty
? ? ?val shouldShowFailedJobs = failedJobs.nonEmpty
? ? ?val completedJobNumStr = if (completedJobs.size == listener.numCompletedJobs) {
? ? ? ?s"${completedJobs.size}"
? ? ?} else {
? ? ? ?s"${listener.numCompletedJobs}, only showing ${completedJobs.size}"
? ? ?}
? ? ?val summary: NodeSeq =
? ? ? ?<div><ul class="unstyled"><li><strong>Total Uptime:strong> // 顯示當(dāng)前Spark應(yīng)用運(yùn)行時(shí)間
? ? ? ? ? ? ?{// 如果還沒有結(jié)束,就用系統(tǒng)當(dāng)前時(shí)間減開始時(shí)間。如果已經(jīng)結(jié)束,就用結(jié)束時(shí)間減開始時(shí)間
? ? ? ? ? ? ? ?if (endTime < 0 && parent.sc.isDefined) {UIUtils.formatDuration(System.currentTimeMillis() - startTime)
? ? ? ? ? ? ? ?} else if (endTime > 0) {
? ? ? ? ? ? ? ? ?UIUtils.formatDuration(endTime - startTime)
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ?}li><li><strong>Scheduling Mode: strong> // 顯示調(diào)度模式,FIFO或FAIR
? ? ? ? ? ? ?{listener.schedulingMode.map(_.toString).getOrElse("Unknown")}li>
? ? ? ? ? ?{
? ? ? ? ? ? ?if (shouldShowActiveJobs) { // 如果有active狀態(tài)的job,則顯示Active Jobs有多少個(gè)<li><a href="#active"><strong>Active Jobs:strong>a>
? ? ? ? ? ? ? ? ?{activeJobs.size}li>
? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?{
? ? ? ? ? ? ?if (shouldShowCompletedJobs) { // 如果有完成狀態(tài)的job,則顯示Completed Jobs的個(gè)數(shù)<li id="completed-summary"><a href="#completed"><strong>Completed Jobs:strong>a>
? ? ? ? ? ? ? ? ?{completedJobNumStr}li>
? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?{
? ? ? ? ? ? ?if (shouldShowFailedJobs) { // 如果有失敗狀態(tài)的job,則顯示Failed Jobs的個(gè)數(shù)<li><a href="#failed"><strong>Failed Jobs:strong>a>
? ? ? ? ? ? ? ? ?{listener.numFailedJobs}li>
? ? ? ? ? ? ?}
? ? ? ? ? ?}ul>div>
? ? ?var content = summary // 將上面的html代碼寫入content變量,在最后統(tǒng)一顯示content中的內(nèi)容
? ? ?val executorListener = parent.executorListener // 這里獲取EventTimeline中的信息
? ? ?content ++= makeTimeline(activeJobs ++ completedJobs ++ failedJobs,
? ? ? ? ?executorListener.executorIdToData, startTime)
// 然后根據(jù)當(dāng)前application中是否存在active, failed, completed狀態(tài)的job,將這些信息顯示在頁(yè)面上。
? ? ?if (shouldShowActiveJobs) {
? ? ? ?content ++= <h4 id="active">Active Jobs ({activeJobs.size})h4> ++
? ? ? ? ?activeJobsTable // 生成active狀態(tài)job的展示表格,具體形式可參看第二部分。按提交時(shí)間倒序排列
? ? ?}
? ? ?if (shouldShowCompletedJobs) {
? ? ? ?content ++= <h4 id="completed">Completed Jobs ({completedJobNumStr})h4> ++
? ? ? ? ?completedJobsTable
? ? ?}
? ? ?if (shouldShowFailedJobs) {
? ? ? ?content ++= <h4 id ="failed">Failed Jobs ({failedJobs.size})h4> ++
? ? ? ? ?failedJobsTable
? ? ?}
? ? ?val helpText = """A job is triggered by an action, like count() or saveAsTextFile().""" +
? ? ? ?" Click on a job to see information about the stages of tasks inside it."
? ? ?UIUtils.headerSparkPage("Spark Jobs", content, parent, helpText = Some(helpText)) // 最后將content中的所有內(nèi)容全部展示在頁(yè)面上
? ?}
?}
接下來以activeJobsTable代碼為例分析Jobs信息展示表格的生成。這里主要的方法是makeRow,接收的是上面代碼中的activeJobs, completedJobs, failedJobs。這三個(gè)對(duì)象都是包含在JobProgressListener對(duì)象中的,在JobProgressListener中的定義如下:
// 這三個(gè)對(duì)象用于存儲(chǔ)數(shù)據(jù)的主要是JobUIData類型,?val activeJobs = new HashMap[JobId, JobUIData]
?val completedJobs = ListBuffer[JobUIData]()
?val failedJobs = ListBuffer[JobUIData]()
將上面三個(gè)對(duì)象傳入到下面這段代碼中,繼續(xù)執(zhí)行。
?private def jobsTable(jobs: Seq[JobUIData]): Seq[Node] = {? ?val someJobHasJobGroup = jobs.exists(_.jobGroup.isDefined)
? ?val columns: Seq[Node] = { // 顯示的信息包括,Job Id(Job Group)以及Job描述,Job提交時(shí)間,Job運(yùn)行時(shí)間,總的Stage/Task數(shù),成功的Stage/Task數(shù),以及一個(gè)進(jìn)度條
? ? ?{if (someJobHasJobGroup) "Job Id (Job Group)" else "Job Id"}
? ? ?Description
? ? ?Submitted
? ? ?Duration
? ? ?class="sorttable_nosort">Stages: Succeeded/Total</th>class="sorttable_nosort">Tasks (for all stages): Succeeded/Total
? ?}
? ?def makeRow(job: JobUIData): Seq[Node] = {
? ? ?val (lastStageName, lastStageDescription) = getLastStageNameAndDescription(job)
? ? ?val duration: Option[Long] = {
? ? ? ?job.submissionTime.map { start => // Job運(yùn)行時(shí)長(zhǎng)為系統(tǒng)時(shí)間,或者結(jié)束時(shí)間減去開始時(shí)間
? ? ? ? ?val end = job.completionTime.getOrElse(System.currentTimeMillis())
? ? ? ? ?end - start
? ? ? ?}
? ? ?}
? ? ?val formattedDuration = duration.map(d => ?// 格式化任務(wù)運(yùn)行時(shí)間,顯示為a h:b m:c s格式UIUtils.formatDuration(d)).getOrElse("Unknown")
? ? ?val formattedSubmissionTime = // 獲取Job提交時(shí)間job.submissionTime.map(UIUtils.formatDate).getOrElse("Unknown")
? ? ?val jobDescription = UIUtils.makeDescription(lastStageDescription, parent.basePath) // 獲取任務(wù)描述
? ? ?val detailUrl = // 點(diǎn)擊單個(gè)Job下面鏈接跳轉(zhuǎn)到JobPage頁(yè)面,傳入?yún)?shù)為jobId
? ? ? ?"%s/jobs/job?id=%s".format(UIUtils.prependBaseUri(parent.basePath), job.jobId)
? ? ?"job-" + job.jobId}>
? ? ? ? ?{job.jobId} {job.jobGroup.map(id => s"($id)").getOrElse("")}
? ? ? ? ?{jobDescription}class="name-link">{lastStageName}-1).toString}>
? ? ? ? ?{formattedSubmissionTime}-1).toString}>{formattedDuration}class="stage-progress-cell">
? ? ? ? ?{job.completedStageIndices.size}/{job.stageIds.size - job.numSkippedStages}
? ? ? ? ?{if (job.numFailedStages > 0) s"(${job.numFailedStages} failed)"}
? ? ? ? ?{if (job.numSkippedStages > 0) s"(${job.numSkippedStages} skipped)"}class="progress-cell"> // 進(jìn)度條
? ? ? ? ?{UIUtils.makeProgressBar(started = job.numActiveTasks, completed = job.numCompletedTasks,
? ? ? ? ? failed = job.numFailedTasks, skipped = job.numSkippedTasks,
? ? ? ? ? total = job.numTasks - job.numSkippedTasks)}
? ?}
? ?class="table table-bordered table-striped table-condensed sortable">{columns}// 顯示列名
? ? ? ?{jobs.map(makeRow)} // 調(diào)用上面的row生成方法,具體顯示Job信息
?}
從上面這些代碼中可以看到,Job頁(yè)面顯示的所有數(shù)據(jù),都是從JobProgressListener對(duì)象中獲得的。SparkUI可以理解成一個(gè)JobProgressListener對(duì)象的消費(fèi)者,頁(yè)面上顯示的內(nèi)容都是JobProgressListener內(nèi)在的展現(xiàn)。?
二、Spark UI界面實(shí)例
默認(rèn)情況下,當(dāng)一個(gè)Spark Application運(yùn)行起來后,可以通過訪問hostname:4040端口來訪問UI界面。hostname是提交任務(wù)的Spark客戶端ip地址,端口號(hào)由參數(shù)spark.ui.port(默認(rèn)值4040,如果被占用則順序往后探查)來確定。由于啟動(dòng)一個(gè)Application就會(huì)生成一個(gè)對(duì)應(yīng)的UI界面,所以如果啟動(dòng)時(shí)默認(rèn)的4040端口號(hào)被占用,則嘗試4041端口,如果還是被占用則嘗試4042,一直找到一個(gè)可用端口號(hào)為止。
下面啟動(dòng)一個(gè)Spark ThriftServer服務(wù),并用beeline命令連接該服務(wù),提交sql語句運(yùn)行。則ThriftServer對(duì)應(yīng)一個(gè)Application,每個(gè)sql語句對(duì)應(yīng)一個(gè)Job,按照J(rèn)ob的邏輯劃分Stage和Task。
1、Jobs頁(yè)面
連接上該端口后,顯示的就是上面的頁(yè)面,也是Job的主頁(yè)面。這里會(huì)顯示所有Active,Completed, Cancled以及Failed狀態(tài)的Job。默認(rèn)情況下總共顯示1000條Job信息,這個(gè)數(shù)值由參數(shù)spark.ui.retainedJobs(默認(rèn)值1000)來確定。
從上面還看到,除了Jobs選項(xiàng)卡之外,還可顯示Stages, Storage, Enviroment, Executors, SQL以及JDBC/ODBC Server選項(xiàng)卡。分別如下圖所示。
2、Stages頁(yè)面
3、Storage頁(yè)面
4、Enviroment頁(yè)面
5、Executors頁(yè)面
6、單個(gè)Job包含的Stages頁(yè)面
7、Task頁(yè)面
作者:淺汐王?
原文:https://blog.csdn.net/qq_32252917/article/details/79934481?
回歸原創(chuàng)文章:
若澤數(shù)據(jù)2018視頻集合
Flink生產(chǎn)最佳實(shí)踐,2018年12月剛出爐
我去過端午、國(guó)慶生產(chǎn)項(xiàng)目線下班,你呢?
2019元旦-線下項(xiàng)目第11期圓滿結(jié)束
大數(shù)據(jù)生產(chǎn)預(yù)警平臺(tái)項(xiàng)目之文章匯總
學(xué)習(xí)大數(shù)據(jù)的路上,別忘了多給自己鼓掌
明年畢業(yè)的我,拿了大數(shù)據(jù)30萬的offer!
最全的Flink部署及開發(fā)案例
我司Kafka+Flink+MySQL生產(chǎn)完整案例代碼
代碼 | Spark讀取mongoDB數(shù)據(jù)寫入Hive普通表和分區(qū)表
我司Spark遷移Hive數(shù)據(jù)到MongoDB生產(chǎn)案例代碼
2019高級(jí)班&線下班報(bào)名咨詢請(qǐng)加
超強(qiáng)干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的结合html做界面_Spark UI界面实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 正态分布的峰度和偏度分别为_ML中的正态
- 下一篇: js中同时得到整数商及余数_js和vue