Spark执行环境——创建度量系统

在SparkEnv中,度量系统是必不可少的一个子组件,其创建代码如下:

//org.apache.spark.SparkEnv
val metricsSystem = if (isDriver) {
  MetricsSystem.createMetricsSystem("driver", conf, securityManager)
} else {
  conf.set("spark.executor.id", executorId)
  val ms = MetricsSystem.createMetricsSystem("executor", conf, securityManager)
  ms.start()
  ms
}

根据代码可知创建度量系统时,将根据当前实例是Driver还是Executor:

  • Driver:创建度量系统,并且制定度量系统的实例名为driver。此时虽然创建了,但是并未启动,目的是等待SparkContext中的任务调度器TaskScheduler告诉度量系统应用程序ID后再启动
  • Executor:设置spark.executor.id属性为当前Executor的ID,然后创建并启动度量系统

MetricsSystem拥有的成员属性如下:

//org.apache.spark.metrics.MetricsSystem
private[this] val metricsConfig = new MetricsConfig(conf)
private val sinks = new mutable.ArrayBuffer[Sink]
private val sources = new mutable.ArrayBuffer[Source]
private val registry = new MetricRegistry()
private var running: Boolean = false
private var metricsServlet: Option[MetricsServlet] = None
  • metricsConfig:度量配置。metricsConfig的类型为MetricsConfig,主要提供对度量配置的设置、加载、转换等功能。
  • sinks:Sink的数组,用来缓存所有注册到MetricSystem的度量输出
  • sources:Source数组,用于缓存所有注册到MetricsSystem的Source
  • registry:度量注册点MetricRegistry,Source和Sink实际都是通过MetricRegistry注册到Metrics的度量仓库中的。
  • running:用于标记当前MetricSystem是否正在运行
  • metricsServlet:metricsServlet将在添加ServletContextHandler后通过WebUI展示

1 MetricsConfig详解

MetricsConfig保存了MetricsSystem的配置信息,有以下两个成员属性:

//org.apache.spark.metrics.MetricsConfig
//度量的属性信息
private[metrics] val properties = new Properties()
//每个实例的子属性
private[metrics] var propertyCategories: mutable.HashMap[String, Properties] = null

1.1 设置默认属性

setDefaultProperties方法用于给MetricsConfig的properties中添加默认的度量属性

//org.apache.spark.metrics.MetricsConfig
private def setDefaultProperties(prop: Properties) {
  prop.setProperty("*.sink.servlet.class", "org.apache.spark.metrics.sink.MetricsServlet")
  prop.setProperty("*.sink.servlet.path", "/metrics/json")
  prop.setProperty("master.sink.servlet.path", "/metrics/master/json")
  prop.setProperty("applications.sink.servlet.path", "/metrics/applications/json")
}

1.2 从文件中加载度量属性

loadPropertiesFromFile用于给MetricsConfig的properties中添加指定文件加载 的度量属性

//org.apache.spark.metrics.MetricsConfig
private[this] def loadPropertiesFromFile(path: Option[String]): Unit = {
  var is: InputStream = null
  try {
    is = path match {
      case Some(f) => new FileInputStream(f)
      case None => Utils.getSparkClassLoader.getResourceAsStream(DEFAULT_METRICS_CONF_FILENAME)
    }
    if (is != null) {
      properties.load(is)
    }
  } catch {
    case e: Exception =>
      val file = path.getOrElse(DEFAULT_METRICS_CONF_FILENAME)
      logError(s"Error loading configuration file $file", e)
  } finally {
    if (is != null) {
      is.close()
    }
  }
}

根据代码,如果没有指定度量属性文件或者此文件不存在,那么将从类路径下的属性文件metrics.properties(即常量DEFAULT_METRICS_CONF_FILENAME)中加载度量属性。

1.3 提取实例的属性

setProperties方法对于properties中的每个属性kv,通过正则表达式匹配找kv的key的前缀和后缀,以前缀为实例,后缀作为 新的属性kv2的key,kv的value作为新的属性kv2的value,最后将属性kv2添加到实例对应的属性集合中作为实例的属性。

//org.apache.spark.metrics.MetricsConfig
def subProperties(prop: Properties, regex: Regex): mutable.HashMap[String, Properties] = {
  val subProperties = new mutable.HashMap[String, Properties]
  prop.asScala.foreach { kv =>
    if (regex.findPrefixOf(kv._1.toString).isDefined) {
      val regex(prefix, suffix) = kv._1.toString
      subProperties.getOrElseUpdate(prefix, new Properties).setProperty(suffix, kv._2.toString)
    }
  }
  subProperties

1.4 初始化

在构造MetricsSystem的过程中将会执行以下代码:

//org.apache.spark.metrics.MetricsSystem
metricsConfig.initialize()

MetricSystem的initialize方法实现代码如下:

//org.apache.spark.metrics.MetricsConfig
def initialize() {
  setDefaultProperties(properties)
  loadPropertiesFromFile(conf.getOption("spark.metrics.conf"))
  val prefix = "spark.metrics.conf."
  conf.getAll.foreach {
    case (k, v) if k.startsWith(prefix) =>
      properties.setProperty(k.substring(prefix.length()), v)
    case _ =>
  }
  propertyCategories = subProperties(properties, INSTANCE_REGEX)
  if (propertyCategories.contains(DEFAULT_PREFIX)) {
    val defaultProperty = propertyCategories(DEFAULT_PREFIX).asScala
    for((inst, prop) <- propertyCategories if (inst != DEFAULT_PREFIX);
        (k, v) <- defaultProperty if (prop.get(k) == null)) {
      prop.put(k, v)
    }
  }
}

其代码实现步骤如下:

  • 1)设置默认属性。
  • 2)从文件中加载度量属性
  • 3)从SparkConf中查找以spark.metric.conf.为前缀的配置属性,并且截取key的前缀后的部分作为度量属性的key/value不变
  • 4)提取实例的属性。
  • 5)向子属性中添加缺失的默认子属性。

2 MetricsSystem中的常用方法

2.1 构建度量源的注册名

buildRegistryName方法用于给Source生成向MetricRegistry中注册的注册名

//org.apache.spark.metrics.MetricsSystem
private[spark] def buildRegistryName(source: Source): String = {
  val appId = conf.getOption("spark.app.id")
  val executorId = conf.getOption("spark.executor.id")
  val defaultName = MetricRegistry.name(source.sourceName)
  if (instance == "driver" || instance == "executor") {
    if (appId.isDefined && executorId.isDefined) {
      MetricRegistry.name(appId.get, executorId.get, source.sourceName)
    } else {
      val warningMsg = s"Using default name $defaultName for source because %s is not set."
      if (appId.isEmpty) { logWarning(warningMsg.format("spark.app.id")) }
      if (executorId.isEmpty) { logWarning(warningMsg.format("spark.executor.id")) }
      defaultName
    }
  } else { defaultName }
}

buildRegistryName方法涉及的变量有几个:

  • executorId:当前Executor的身份标识,通过读取spark.executor.id属性获得
  • defaultName:调用MetricRegistry的name方法生成的默认注册名

其执行步骤如下:

  • 1)如果度量系统的实例名为driver或executor,那么调用MetricRegistry的name方法生成注册名
  • 2)如果度量系统的实例名没有定义,则采用defaultName为注册名

2.2 注册度量源

registerSource方法用于向MetricsSystem中注册度量源

//org.apache.spark.metrics.MetricsSystem
def registerSource(source: Source) {
  sources += source
  try {
    val regName = buildRegistryName(source)
    registry.register(regName, source.metricRegistry)
  } catch {
    case e: IllegalArgumentException => logInfo("Metrics already registered", e)
  }
}

2.3 获取ServletContextHandler

为了将度量系统和Spark UI结合起来使用,即将Spark UI的Web展现也作为度量系统的Sink之一,我们需要一个ServletContextHandler能在Spark UI中接收请求,并且还需要能将度量系统的Sink之一,我们需要一个ServletContextHandler能在SparkUI中接收请求,并且还需要能将度量输出到Spark UI的页面。MetricsServlet是特质Sink的实现之一,但是它还不是一个ServletContextHandler,因此需要有一个转换,代码如下:

//org.apache.spark.metrics.MetricsSystem
def getServletHandlers: Array[ServletContextHandler] = {
  require(running, "Can only call getServletHandlers on a running MetricsSystem")
  metricsServlet.map(_.getHandlers(conf)).getOrElse(Array())
}

上述代码中调用了MetricServlet的getHandlers方法来实现转换,getHandlers的实现如代码:

//org.apache.spark.metrics.MetricsSystem
def getHandlers(conf: SparkConf): Array[ServletContextHandler] = {
  Array[ServletContextHandler](
    createServletHandler(servletPath,
      new ServletParams(request => getMetricsSnapshot(request), "text/json"), securityMgr, conf)
  )
}

getHandlers也调用了JettyUtils的createServletHandler方法创建ServletContextHandler。

3 启动MetricsSystem

SparkEnv会调用MetricsSystem的start方法启动MetricsSystem,start方法的实现代码如下:

//org.apache.spark.metrics.MetricsSystem
def start() {
  require(!running, "Attempting to start a MetricsSystem that is already running")
  running = true
  StaticSources.allSources.foreach(registerSource)
  registerSources()
  registerSinks()
  sinks.foreach(_.start)
}

根据代码,启动MetricsSystem的步骤如下:

  • 1)将当前MetricsSystem的running字段设置为true,即表示MetricsSystem已经处于运行状态
  • 2)将静态的度量来源CodegenMetrics注册到MetricRegistry。allSources是一个序列,定义如下:
//org.apache.spark.metrics.source.StaticSources
private[spark] object StaticSources {
  val allSources = Seq(CodegenMetrics)
}
  • 3)从初始化完成的MetricsConfig中获取当前实例的度量来源属性,并调用registerSources方法,将这些度量来源注册到MetricsRegistry。
//org.apache.spark.metrics.MetricsSystem
private def registerSources() {
  val instConfig = metricsConfig.getInstance(instance)
  val sourceConfigs = metricsConfig.subProperties(instConfig, MetricsSystem.SOURCE_REGEX)
  sourceConfigs.foreach { kv =>
    val classPath = kv._2.getProperty("class")
    try {
      val source = Utils.classForName(classPath).newInstance()
      registerSource(source.asInstanceOf[Source])
    } catch {
      case e: Exception => logError("Source class " + classPath + " cannot be instantiated", e)
    }
  }
}

根据上述代码清单,注册度量配置中度量源的步骤如下:

  • ①获取当前实例的度量属性。根据MetricsConfig的getInstance方法的逻辑,当实例不存在时返回默认实例的。以driver来讲,默认不存在driver的实例属性,因此返回 * 对应的属性,即:

*->{

  sink.servlet.class=org.apache.spark.metrics.sink.MetricsServlet

  sink.servlet.path=/metrics/json

}

  • ②匹配正则表达式^source\\.(.+)\\.(.+),获取所有度量源更细粒度的实例及属性

  • ③使用实例的class属性,通过Java反射生成度量源的实例,并调用registerSource方法将此度量源注册到MetricRegistry

  • 4)从初始化完成的MetricsConfig中获取当前实例的度量输出属性,并将这些度量输出注册到sinks。

//org.apache.spark.metrics.MetricsSystem
private def registerSinks() {
  val instConfig = metricsConfig.getInstance(instance)
  val sinkConfigs = metricsConfig.subProperties(instConfig, MetricsSystem.SINK_REGEX)
  sinkConfigs.foreach { kv =>
    val classPath = kv._2.getProperty("class")
    if (null != classPath) {
      try {
        val sink = Utils.classForName(classPath)
          .getConstructor(classOf[Properties], classOf[MetricRegistry], classOf[SecurityManager])
          .newInstance(kv._2, registry, securityMgr)
        if (kv._1 == "servlet") {
          metricsServlet = Some(sink.asInstanceOf[MetricsServlet])
        } else {
          sinks += sink.asInstanceOf[Sink]
        }
      } catch {
        case e: Exception =>
          logError("Sink class " + classPath + " cannot be instantiated")
          throw e
      }
    }
  }
}

根据代码,注册度量配置中度量输出的步骤如下:

  • ① 获取当前实例的度量属性。根据MetricsConfig的getInstance方法的逻辑,当实例不存在时将返回默认实例的属性。以driver实例来讲,默认不存在driver的实例属性,因此返回 * 对应的属性,即

*->{

  sink.servlet.class=org.apache.spark.metrics.sink.MetricsServlet

  sink.servlet.path=/metrics/json

}

  • ②匹配正则表达式^sink\.(.+)\.(.+),获取所有度量输出更细粒度的实例及属性。根据subProperties方法的逻辑,最后得到:

Map(servlet -> {class=org.spark.metrics.sink.MetricsServlet,path=/metrics/json})

  • ③使用实例的class属性,通过Java反射生成度量输出实例。如果当前实例是servlet,则由mericsServlet持有此servlet的引用,否则将度量输出实例注册到数组缓冲sinks中
  • ④启动sinks中的全部度量输出实例
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值