Spark2.1.0——深入浅出度量系统

  对于一个系统而言,首先考虑要满足一些业务场景,并实现功能。随着系统功能越来越多,代码量级越来越高,系统的可维护性、可测试性、性能都会成为新的挑战,这时监控功能就变得越来越重要了。在国内,绝大多数IT公司的项目都以业务为导向,以完成功能为目标,这些项目在立项、设计、开发、上线的各个阶段,很少有人会考虑到监控的问题。在国内,开发人员能够认真的在代码段落中打印日志,就已经属于最优秀的程序员了。然而,在国外的很多项目则不会这样,看看久负盛名的Hadoop的监控系统就可见一斑,尤其是在Facebook,更是把功能、日志以及监控列为同等重要,作为一个合格工程师的三驾马车。

  Spark作为优秀的开源系统,在监控方面也有自己的一整套体系。一个系统有了监控功能后将收获诸多益处,如可测试性、性能优化、运维评估、数据统计等。Spark的度量系统使用codahale提供的第三方度量仓库Metrics,本节将着重介绍Spark基于Metrics构建度量系统的原理与实现。对于Metrics感兴趣的读者,可以参考阅读《附录D Metrics简介》中的内容。

  Spark的度量系统中有三个概念:

  • Instance:指定了度量系统的实例名。Spark按照Instance的不同,区分为Master、Worker、Application、Driver和Executor;
  • Source:指定了从哪里收集度量数据,即度量数据的来源。Spark提供了应用的度量来源(ApplicationSource)、Worker的度量来源(WorkerSource)、DAGScheduler的度量来源(DAGSchedulerSource)、BlockManager的度量来源(BlockManagerSource)等诸多实现,对各个服务或组件进行监控。
  • Sink:指定了往哪里输出度量数据,即度量数据的输出。Spark中使用MetricsServlet作为默认的Sink,此外还提供了ConsoleSink、CsvSink、JmxSink、MetricsServlet、GraphiteSink等实现。

为了更加直观的表现上述概念,我们以图1来表示Spark中度量系统的工作流程。

图1 度量系统的工作流程

Source继承体系

  任何监控都离不开度量数据的采集,离线的数据采集很容易做到和被采集模块之间的解耦,但是对于实时度量数据,尤其是那些内存中数据的采集就很难解耦。这就类似于网页监控数据的埋点一样,你要在网页中加入一段额外的js代码(例如Google分析,即便你只是引入一个js文件,这很难让前端工程师感到开心)。还有一类监控,比如在Java Web中增加一个负责监控的Servlet或者一个基于Spring3.0的拦截器,这种方式虽然将耦合度从代码级别降低到配置级别,但却无法有效的对内存中的数据结构进行监控。Spark的度量系统对系统功能来说是在代码层面耦合的,这种牺牲对于能够换取对实时的、处于内存中的数据进行更有效的监控是值得的。

  Spark将度量来源抽象为Source,其定义见代码清单1。

代码清单1         度量源的定义

private[spark] trait Source {
def sourceName: String
def metricRegistry: MetricRegistry
}

  Spark中有很多Source的具体实现,可以通过图2来了解。

图2    Source的继承体系

为了说明Source该如何实现,我们选择ApplicationSource(也是因为其实现简单明了,足以说明问题)为例,其实现见代码清单2。

代码清单2         ApplicationSource的实现

private[master] class ApplicationSource(val application: ApplicationInfo) extends Source {
override val metricRegistry = new MetricRegistry()
override val sourceName = "%s.%s.%s".format("application", application.desc.name,
System.currentTimeMillis()) metricRegistry.register(MetricRegistry.name("status"), new Gauge[String] {
override def getValue: String = application.state.toString
}) metricRegistry.register(MetricRegistry.name("runtime_ms"), new Gauge[Long] {
override def getValue: Long = application.duration
}) metricRegistry.register(MetricRegistry.name("cores"), new Gauge[Int] {
override def getValue: Int = application.coresGranted
}) }

  望文生义,ApplicationSource用于采集Spark应用程序相关的度量。代码清单2中ApplicationSource重载了metricRegistry和sourceName,并且向自身的注册表注册了status(即应用状态,包括:WAITING, RUNNING, FINISHED, FAILED, KILLED, UNKNOWN)、runtime_ms(运行持续时长)、cores(授权的内核数)等度量。这三个度量的取值分别来自于ApplicationInfo的state、duration和coresGranted三个属性。这三个度量都由Gauge的匿名内部类实现,Gauge是Metrics提供的用于估计度量值的特质。有关Gauge、MetricRegistry、MetricRegistry注册度量的方法register及命名方法name的更详细介绍请阅读《附录D Metrics简介》。

Sink继承体系

  Source准备好度量数据后,我们就需要考虑如何输出和使用的问题。这里介绍一些常见的度量输出方式:阿里数据部门采用的一种度量使用方式就是输出到日志;在命令行运行过Hadoop任务(例如:mapreduce)的使用者也会发现控制台打印的内容中也包含度量信息;用户可能希望将有些度量信息保存到文件(例如CSV),以便将来能够查看;如果觉得使用CSV或者控制台等方式不够直观,还可以将采集到的度量数据输出到专用的监控系统界面。这些最终对度量数据的使用,或者说是输出方式,Spark将它们统一抽象为Sink。Sink的定义见代码清单3。

代码清单3         度量输出的定义

private[spark] trait Sink {
def start(): Unit
def stop(): Unit
def report(): Unit
}  

从代码清单3可以看到Sink是一个特质,包含三个接口方法:

  • start:启动Sink;
  • stop:停止Sink;
  • report:输出到目的地;

从这三个方法的解释来看,很难让读者获得更多的信息。我们先把这些困惑放在一边,来看看Spark中Sink的类继承体系,如图3所示。

图3     Sink的类继承体系

图3中展示了6种Sink的具体实现。

  • ConsoleSink:借助Metrics提供的ConsoleReporter的API,将度量输出到System.out,因此可以输出到控制台。
  • CsvSink:借助Metrics提供的CsvReporter的API,将度量输出到CSV文件。
  • MetricsServlet:在Spark UI的jetty服务中创建ServletContextHandler,将度量数据通过Spark UI展示在浏览器中。
  • JmxSink:借助Metrics提供的JmxReporter的API,将度量输出到MBean中,这样就可以打开Java VisualVM,然后打开Tomcat进程监控,给VisualVM安装MBeans插件后,选择MBeans标签页可以对JmxSink所有注册到JMX中的对象进行管理。
  • Slf4jSink:借助Metrics提供的Slf4jReporter的API,将度量输出到实现了Slf4j规范的日志输出。
  • GraphiteSink:借助Metrics提供的GraphiteReporter的API,将度量输出到Graphite(一个由Python实现的Web应用,采用django框架,用来收集服务器状态的监控系统)。

了解了Sink的类继承体系,我们挑选Slf4jSink作为Spark中Sink实现类的例子,来了解Sink具体该如何实现。Slf4jSink的实现见代码清单4。

代码清单4         Slf4jSink的实现

private[spark] class Slf4jSink(
val property: Properties,
val registry: MetricRegistry,
securityMgr: SecurityManager)
extends Sink {
val SLF4J_DEFAULT_PERIOD = 10
val SLF4J_DEFAULT_UNIT = "SECONDS" val SLF4J_KEY_PERIOD = "period"
val SLF4J_KEY_UNIT = "unit" val pollPeriod = Option(property.getProperty(SLF4J_KEY_PERIOD)) match {
case Some(s) => s.toInt
case None => SLF4J_DEFAULT_PERIOD
} val pollUnit: TimeUnit = Option(property.getProperty(SLF4J_KEY_UNIT)) match {
case Some(s) => TimeUnit.valueOf(s.toUpperCase())
case None => TimeUnit.valueOf(SLF4J_DEFAULT_UNIT)
} MetricsSystem.checkMinimalPollingPeriod(pollUnit, pollPeriod) val reporter: Slf4jReporter = Slf4jReporter.forRegistry(registry)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.convertRatesTo(TimeUnit.SECONDS)
.build() override def start() {
reporter.start(pollPeriod, pollUnit)
} override def stop() {
reporter.stop()
} override def report() {
reporter.report()
}
}

  从Slf4jSink的实现可以看到Slf4jSink的start、stop及report实际都是代理了Metrics库中的Slf4jReporter的start、stop及report方法。Slf4jReporter的start方法实际是其父类ScheduledReporter的start实现。而传递的两个参数pollPeriod和pollUnit,正是被ScheduledReporter使用作为定时器获取数据的周期和时间单位。有关ScheduledReporter中start、stop及Slf4jReporter的report方法的实现可以参阅《附录D Metrics简介》。

关于《Spark内核设计的艺术 架构设计与实现》

经过近一年的准备,基于Spark2.1.0版本的《Spark内核设计的艺术 架构设计与实现》一书现已出版发行,图书如图:

纸质版售卖链接如下:

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

Spark2.1.0——深入浅出度量系统的更多相关文章

  1. Spark2.1.0模型设计与基本架构(上)

    随着近十年互联网的迅猛发展,越来越多的人融入了互联网——利用搜索引擎查询词条或问题:社交圈子从现实搬到了Facebook.Twitter.微信等社交平台上:女孩子们现在少了逛街,多了在各大电商平台上的 ...

  2. 深入浅出 - Android系统移植与平台开发(一)

    深入浅出 - Android系统移植与平台开发(一) 分类: Android移植2012-09-05 14:16 16173人阅读 评论(12) 收藏 举报 androidgitgooglejdkub ...

  3. Hadoop2.7.3+Spark2.1.0完全分布式集群搭建过程

    1.选取三台服务器(CentOS系统64位) 114.55.246.88 主节点 114.55.246.77 从节点 114.55.246.93 从节点 之后的操作如果是用普通用户操作的话也必须知道r ...

  4. spark-2.2.0安装和部署——Spark集群学习日记

    前言 在安装后hadoop之后,接下来需要安装的就是Spark. scala-2.11.7下载与安装 具体步骤参见上一篇博文 Spark下载 为了方便,我直接是进入到了/usr/local文件夹下面进 ...

  5. Spark2.1.0——运行环境准备

    学习一个工具的最好途径,就是使用它.这就好比<极品飞车>玩得好的同学,未必真的会开车,要学习车的驾驶技能,就必须用手触摸方向盘.用脚感受刹车与油门的力道.在IT领域,在深入了解一个系统的原 ...

  6. Spark2.1.0——Spark初体验

    学习一个工具的最好途径,就是使用它.这就好比<极品飞车>玩得好的同学,未必真的会开车,要学习车的驾驶技能,就必须用手触摸方向盘.用脚感受刹车与油门的力道.在IT领域,在深入了解一个系统的原 ...

  7. Hadoop 3.1.2(HA)+Zookeeper3.4.13+Hbase1.4.9(HA)+Hive2.3.4+Spark2.4.0(HA)高可用集群搭建

    目录 目录 1.前言 1.1.什么是 Hadoop? 1.1.1.什么是 YARN? 1.2.什么是 Zookeeper? 1.3.什么是 Hbase? 1.4.什么是 Hive 1.5.什么是 Sp ...

  8. Spark2.1.0——内置Web框架详解

    Spark2.1.0——内置Web框架详解 任何系统都需要提供监控功能,否则在运行期间发生一些异常时,我们将会束手无策.也许有人说,可以增加日志来解决这个问题.日志只能解决你的程序逻辑在运行期的监控, ...

  9. Spark2.1.0——深入理解事件总线

    Spark2.1.0——深入理解事件总线 概览 Spark程序在运行的过程中,Driver端的很多功能都依赖于事件的传递和处理,而事件总线在这中间发挥着至关重要的纽带作用.事件总线通过异步线程,提高了 ...

随机推荐

  1. js-选项卡套选项卡

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  2. Python自动化开发 - 面向对象(二)

    本节内容 1.isinstance(obj,cls)和issubclass(sub,super) 2.反射 3.__setattr__,__delattr__,__getattr__ 一. isins ...

  3. Convolution Neural Network (CNN) 原理与实现

    本文结合Deep learning的一个应用,Convolution Neural Network 进行一些基本应用,参考Lecun的Document 0.1进行部分拓展,与结果展示(in pytho ...

  4. 拟物设计和Angular的实现 - Material Design

    Material Design是Google最新发布的跨平台统一视觉设计语言.直接翻译是物质设计,但是我更倾向于使用"拟物设计"更为准确. 据谷歌介绍,Material Desig ...

  5. this与$(this)对象

    this与$(this)对象.前者是Javascript对象,而后者是jQuery是对象.两者分清楚,它们只能使用自己的方法.Javascript对象使用Javascript的方法,jQuery对象使 ...

  6. c++中的一些计算的问题

    要实现小数的四舍五入, float a = 3.456; //保留到小数点后两位 float b =(int)((a * 100) + 0.5) / 100.0; 但是这样对负数不好使, 对负数的话, ...

  7. “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. Linux Shell脚本编程提高(12)

    实际上Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核,不仅如此,Shell有自己的编程语言用于对命令的编辑,它允许用户编写由shell命令组成的程序.Shel编程语言具有普通编程 ...

  9. 使用httpClient模拟http请求

    在很多场景下都需要用到java代码来发送http请求:如和短信后台接口的数据发送,发送数据到微信后台接口中: 这里以apache下的httpClient类来模拟http请求:以get和Post请求为例 ...

  10. oracle数据库迁移相关

    常见的实现方式: rman exp/imp  expdp/impdp DG OGG 主要是看停机时间了,方法很多,数据量小,就导出,如果时间要求很高,那可以采取dg或ogg或类似的技术.减低downt ...