spark-core_11:org.apache.spark.deploy.master.Master源碼解析3--MasterWebUI(MasterRpcEndPoint,8080)初始化web

承接上文

分析一下newMasterWebUI(MasterRpcEndPoint,8080):它的主要作用就是將每個頁面的html以scala.xml.Node的形勢封裝放在serlvet中,然後再將servlet放到servletContextHandler中,供jetty.Server使用

/**
 * Web UI server for the standalonemaster.
  * 實現抽象類WebUI的子類有:MasterWebUI,HistoryServer,MesosClusterUI, SparkUI, WorkerWebUI。這些都是真正提供web ui服務的。
 */

private[master]
class MasterWebUI(
   
val master:Master,
   
requestedPort: Int,
   
customMasterPage: Option[MasterPage] = None)
 
extends WebUI(master.securityMgr, requestedPort, master.conf, name = "MasterUI") with Logging
 
with UIRoot{

 
val masterEndpointRef= master.self
 
val killEnabled= master.conf.getBoolean("spark.ui.killEnabled", true)

 
val masterPage= customMasterPage.getOrElse(new MasterPage(this))
 
initialize()
  /** Initialize all components of the server.
    * 初始化Master UI的web組件: ServletContextHandler
    * ===
    * MasterPage、ApplicationPage、HistoryNotFoundPage他們父類是
    * WebUIPage有兩個抽象函數render(request: HttpServletRequest): Seq[Node]和renderJson(request: HttpServletRequest): JValue。
    * 它兩個方法都被attachPage()裏面的方法,進行調用
    * */

 
def initialize() {
   
//MasterPage路徑: / ,可以跟進去看WebUIPage主構造參數就是它的路徑
    val masterPage= new MasterPage(this//MasterPage:渲染workers, 活躍的appliction,活躍的drivers
    //ApplicationPage路徑:/app
    attachPage(new ApplicationPage(this)) //ApplicationPage根據request中的appId參數, 展示這個活躍的application
    attachPage(new HistoryNotFoundPage(this)) //HistoryNotFoundPage路徑 /history/not-found
    attachPage(masterPage)
   
//加載靜態資源, 見這裏:resources/org/apache/spark/ui/static/
    attachHandler(createStaticHandler(MasterWebUI.STATIC_RESOURCE_DIR, "/static"))
   
attachHandler(ApiRootResource.getServletHandler(this))
    //把這倆kill請求轉發給master page處理, Master異步kill,也就是從頁面上可以停止app和driver, 路徑:/app/kill, /driver/kill
    attachHandler(createRedirectHandler(
     
"/app/kill", "/", masterPage.handleAppKillRequest, httpMethods = Set("POST")))
   
attachHandler(createRedirectHandler(
      "/driver/kill", "/", masterPage.handleDriverKillRequest, httpMethods= Set("POST")))
 
}

1,分析一下new MasterPage(this),它的render()方法用scala.xml的方式將html構造出來

/**
  * WebUIPage有兩個抽象函數render(request:HttpServletRequest): Seq[Node]和renderJson(request:HttpServletRequest): JValue。
  * 需要注意的是成員參數prefix,這個就是這個頁面對應的http path了
  * 看參數可以猜出, 接收servlet request, 處理請求, 返回Node或者Json結果。
  * 它的子類有MasterPage、ApplicationPage等
  */

private[ui] class MasterPage(parent:MasterWebUI) extends WebUIPage("") {
 
private val master =parent.masterEndpointRef

 
….
 
/** Index view listing applications and executors
    * 一大堆的html代碼, 補上所需的變量, 加上公共部分頁頭UIUtils.basicSparkPage, 就是一個完整的渲染後的頁面了
    * 它會被MasterWebUI初始化後,被initialize()方法的attachPage()調用==>再被createServletHandler方法調用(它是創建jetty的servletContextHandler用的)
    * ==》然後這個過程使用createServlet創建servlet
    * */

 
def render(request: HttpServletRequest): Seq[Node] = {
   
val state= getMasterState
   
//worker列表上的列字段
    val workerHeaders= Seq("WorkerId", "Address", "State", "Cores", "Memory")
   
val workers= state.workers.sortBy(_.id)
   
val aliveWorkers= state.workers.filter(_.state== WorkerState.ALIVE)
   
val workerTable= UIUtils.listingTable(workerHeaders, workerRow, workers)

   
val appHeaders= Seq("ApplicationID", "Name", "Cores", "Memory per Node", "Submitted Time",
     
"User", "State", "Duration")
   
val activeApps= state.activeApps.sortBy(_.startTime).reverse
   
val activeAppsTable= UIUtils.listingTable(appHeaders, appRow, activeApps)
   
val completedApps= state.completedApps.sortBy(_.endTime).reverse
   
val completedAppsTable= UIUtils.listingTable(appHeaders, appRow, completedApps)

   
val driverHeaders= Seq("SubmissionID", "Submitted Time", "Worker", "State", "Cores",
     
"Memory", "MainClass")
   
val activeDrivers= state.activeDrivers.sortBy(_.startTime).reverse
   
val activeDriversTable= UIUtils.listingTable(driverHeaders, driverRow, activeDrivers)
   
val completedDrivers= state.completedDrivers.sortBy(_.startTime).reverse
   
val completedDriversTable= UIUtils.listingTable(driverHeaders, driverRow, completedDrivers)

   
// For now we only show driver information if the userhas submitted drivers to the cluster.
   
// This is until we integrate thenotion of drivers and applications in the UI.
    def hasDrivers: Boolean =activeDrivers.length > 0 || completedDrivers.length > 0
   
//scala的xml和函數一樣是一等公民
    //使用花括號操作scala成員

    val content=
       
<div class="row-fluid">
         
<div class="span12">
           
<ul class="unstyled">
             
<li><strong>URL:</strong> {state.uri}</li>
             
{
               
state.restUri.map { uri=>
                  <li>
                   
<strong>REST URL:</strong> {uri}
                   
<span class="rest-uri"> (cluster mode)</span>
                 
</li>
                }.getOrElse { Seq.empty}
             
}
              。。。。            </ul>
          </div>
        </div>

        。。。。。       </div>;
   
UIUtils.basicSparkPage(content, "Spark Master at " + state.uri)
 
}

2,再回MasterWebUI的initialize()中attachPage(masterPage)

/** Attach apage to this UI.
  * WebUIPage的子類有:MasterPage\ApplicationPage\HistoryNotFoundPage
  * */

def attachPage(page: WebUIPage) {
 
//pagePath是servlet的訪問路徑
  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)

  pageToHandlers.getOrElseUpdate(page, ArrayBuffer[ServletContextHandler]())
   
.append(renderHandler)
}

===> createServletHandler()就是用創建ServletContextHandler

/** Create acontext handler that responds to a request with the given path prefix
  * 傳進來的第一個參數就是servlet的url
  * 第二個參數是(request:HttpServletRequest) => page.render(request)。它變成ServletParams是因爲有隱式轉換,

private[spark] object JettyUtils extendsLogging {
  type Responder[T] =HttpServletRequest => T
  class ServletParams[T <% AnyRef](valresponder: Responder[T],
    val contentType: String,
    val extractFn: T => String= (in: Any) => in.toString) {}

  implicit def htmlResponderToServlet(responder:Responder[Seq[Node]]): ServletParams[Seq[Node]] =
    new ServletParams(responder,"text/html", (in: Seq[Node]) => "<!DOCTYPE html>"+ in.toString)

將這個匿名函數隱式成了ServletParams目的是爲了得到ServletContextHandler,給jetty的server使用
  * */

def createServletHandler[T <% AnyRef](
   
path: String,
   
servletParams: ServletParams[T],
   
securityMgr: SecurityManager,
   
conf: SparkConf,
   
basePath: String= ""): ServletContextHandler = {
 
createServletHandler(path, createServlet(servletParams, securityMgr, conf), basePath)
}

/** Create acontext handler that responds to a request with the given path prefix
  * 真正創建ServletContextHandler的地方,這個ServletContextHandler,可以按setContextPath()設置上下文路徑
  * */

def createServletHandler(
   
path: String,
   
servlet: HttpServlet,
   
basePath: String): ServletContextHandler = {
 
val prefixedPath= if (basePath == "" && path== "/") {
   
path
  } else {
   
//將傳進來是項目路徑最後一個“/”去掉
    (basePath + path).stripSuffix("/")
 
}
  val contextHandler= new ServletContextHandler
 
val holder= new ServletHolder(servlet) //ServletHolder就是用來承載servlet
  contextHandler.setContextPath(prefixedPath)
 
contextHandler.addServlet(holder, "/"//即通過prefixedPath+“/”就可以訪問到這個servlet
  contextHandler
}

 

===》再看一下createServlet(servletParams, securityMgr, conf)是如何創建Servlet的

def createServlet[T <% AnyRef](
   
servletParams: ServletParams[T],
   
securityMgr: SecurityManager,
   
conf: SparkConf): HttpServlet = {
 
// SPARK-10589 avoid frame-related click-jackingvulnerability, using X-Frame-Options
  // (seehttp://tools.ietf.org/html/rfc7034). By default allow framing only from the
  // same origin, but allow framing for aspecific named URI.
  // Example: spark.ui.allowFramingFrom =https://example.com/
  //X-Frame-Options用於設置頁面是否可以被放在iframe中,其中SAMEORIGIN表示只能本網站進行iframe
 
/**
    * Java代碼:
     response.addHeader("x-frame-options","SAMEORIGIN");
      Nginx配置:
      add_header X-Frame-OptionsSAMEORIGIN
      Apache配置:
      Header always appendX-Frame-Options SAMEORIGIN
    */

 
val allowFramingFrom= conf.getOption("spark.ui.allowFramingFrom")
 
val xFrameOptionsValue=
   
allowFramingFrom.map(uri => s"ALLOW-FROM $uri").getOrElse("SAMEORIGIN")

 
new HttpServlet{
   
override def doGet(request:HttpServletRequest, response: HttpServletResponse) {
     
try {
       
//默認是true,SecurityManager是關於用戶權限和ssl相關的管理類
        if (securityMgr.checkUIViewPermissions(request.getRemoteUser)){
         
//這個ServletParams是隱式轉換過來的,如果"text/html"表示解析成html,text/plain解析成源碼,text/json解析成json格式
          response.setContentType("%s;charset=utf-8".format(servletParams.contentType))
         
response.setStatus(HttpServletResponse.SC_OK) //響應狀態
         
//調用(request:HttpServletRequest) => page.render(request),將會得到Seq[scala.xml.Node]
          val result = servletParams.responder(request)
         
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate")
         
response.setHeader("X-Frame-Options", xFrameOptionsValue)
         
// scalastyle:off println
         
//通過response管道輸出Seq[scala.xml.Node],extractFn():可以將Seq[Node]變成字符串
          response.getWriter.println(servletParams.extractFn(result))
         
// scalastyle:on println
       
} else{
         
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
         
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate")
         
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
           
"User is not authorized to access this page.")
       
}
      } catch {
       
case e: IllegalArgumentException =>
         
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage)
       
case e: Exception =>
 
        logWarning(s"GET ${request.getRequestURI} failed: $e", e)
         
throw e
     
}
    }
    // SPARK-5983 ensure TRACE is not supported
   
protected override def doTrace(req:HttpServletRequest, res: HttpServletResponse): Unit = {
     
res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED)
   
}
  }
}

3,MasterWebUI的initialize()中attachPage(masterPage){…attachHandler(renderHandler)。。}這個attachHandler()就是將返回的ServletContextHandler加到一個ArrayBuffer[ServletContextHandler]

====》到此,MasterWebUI初始化代碼就結束了,還回到Master中

webUi.bind() //啓動一個jetty ,生成server實例, 監聽web端口, jetty server綁定handler

五、看如何啓動的jetty.Server的

/** Bind to theHTTP server behind this web interface. */
def bind() {
 
assert(!serverInfo.isDefined, "Attempted to bind %s more thanonce!".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)
 
}
}

 

  /**
   * 嘗試使用給定的上下文處理程序啓動綁定到提供的hostName:port的Jetty服務器。

   如果所需的端口號被爭用,則繼續增加端口直到找到空閒端口。 返回碼頭服務器對象,選定的端口以及可變的處理程序集合。
   */

 
def startJettyServer(
     
hostName: String,
     
port: Int,
     
handlers: Seq[ServletContextHandler],
     
conf: SparkConf,
     
serverName: String = ""): ServerInfo = {

   
addFilters(handlers, conf)
    //這個ContextHandlerCollection會對每個Handler進行執行,即便某個Handler失敗,後面的Handler也會執行
   
val collection= new ContextHandlerCollection
  
 /**
      * 壓縮內容可以極大地提高網絡帶寬使用率,但是會以內存和CPU週期爲代價。如果此處理程序用於靜態內容, 那麼可以避免使用高效的直接NIO,因此建議使用
<code>org.eclipse.jetty.servlet.DefaultServlet</
code>的gzip機制
      */

   
val gzipHandlers = handlers.map { h =>
     
val gzipHandler= new GzipHandler
     
gzipHandler.setHandler(h)
      gzipHandler

    }
   collection.setHandlers(gzipHandlers.toArray)


    // Bind to the given port, or throw ajava.net.BindException if the port is occupied
   
def connect(currentPort: Int):(Server, Int) = {
    
 //給server設置InetSocketAddress對應的host及port
      val server= new Server(new InetSocketAddress(hostName, currentPort))
     
val pool= new QueuedThreadPool
     
pool.setDaemon(true)
      server.setThreadPool(pool)
//給server定義聯接池
      val errorHandler= new ErrorHandler()
     
errorHandler.setShowStacks(true)
      server.addBean(errorHandler)
      server.setHandler(collection)

      try {
       
server.start()
        (server, server.getConnectors.head.getLocalPort)
     
} catch {
       
case e: Exception =>
     
    server.stop()
          pool.stop()
          throw e
     
}
    }
    //就是將上面的(server, server.getConnectors.head.getLocalPort)返回
   
val (server, boundPort) = Utils.startServiceOnPort[Server](port, connect, conf, serverName)
   
ServerInfo(server, boundPort, collection)
 
}
}

到此JettyServer啓動結束,再回到Master中看如何清理超時的Woker及zk是如何選舉


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章