Spark2.1.0——深入浅出度量系统
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——深入浅出度量系统的更多相关文章
- Spark2.1.0模型设计与基本架构(上)
随着近十年互联网的迅猛发展,越来越多的人融入了互联网——利用搜索引擎查询词条或问题:社交圈子从现实搬到了Facebook.Twitter.微信等社交平台上:女孩子们现在少了逛街,多了在各大电商平台上的 ...
- 深入浅出 - Android系统移植与平台开发(一)
深入浅出 - Android系统移植与平台开发(一) 分类: Android移植2012-09-05 14:16 16173人阅读 评论(12) 收藏 举报 androidgitgooglejdkub ...
- Hadoop2.7.3+Spark2.1.0完全分布式集群搭建过程
1.选取三台服务器(CentOS系统64位) 114.55.246.88 主节点 114.55.246.77 从节点 114.55.246.93 从节点 之后的操作如果是用普通用户操作的话也必须知道r ...
- spark-2.2.0安装和部署——Spark集群学习日记
前言 在安装后hadoop之后,接下来需要安装的就是Spark. scala-2.11.7下载与安装 具体步骤参见上一篇博文 Spark下载 为了方便,我直接是进入到了/usr/local文件夹下面进 ...
- Spark2.1.0——运行环境准备
学习一个工具的最好途径,就是使用它.这就好比<极品飞车>玩得好的同学,未必真的会开车,要学习车的驾驶技能,就必须用手触摸方向盘.用脚感受刹车与油门的力道.在IT领域,在深入了解一个系统的原 ...
- Spark2.1.0——Spark初体验
学习一个工具的最好途径,就是使用它.这就好比<极品飞车>玩得好的同学,未必真的会开车,要学习车的驾驶技能,就必须用手触摸方向盘.用脚感受刹车与油门的力道.在IT领域,在深入了解一个系统的原 ...
- 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 ...
- Spark2.1.0——内置Web框架详解
Spark2.1.0——内置Web框架详解 任何系统都需要提供监控功能,否则在运行期间发生一些异常时,我们将会束手无策.也许有人说,可以增加日志来解决这个问题.日志只能解决你的程序逻辑在运行期的监控, ...
- Spark2.1.0——深入理解事件总线
Spark2.1.0——深入理解事件总线 概览 Spark程序在运行的过程中,Driver端的很多功能都依赖于事件的传递和处理,而事件总线在这中间发挥着至关重要的纽带作用.事件总线通过异步线程,提高了 ...
随机推荐
- 修复msvcp120.dll
https://www.microsoft.com/zh-CN/download/details.aspx?id=40784
- CAS Ticket票据:TGT、ST、PGT、PT、PGTIOU
CAS的核心就是其Ticket,及其在Ticket之上的一系列处理操作.CAS的主要票据有TGT.ST.PGT.PGTIOU.PT,其中TGT.ST是CAS1.0协议中就有的票据,PGT.PGTIOU ...
- 介绍 Scratch 3.0:扩展编码创造力
在过去十年中,全世界数百万儿童使用Scratch编写自己的互动游戏,故事,动画等. 这种磅礴的创造力激励我们继续扩展和改进Scratch,让世界各地的孩子都有新的机会用新技术创造性地表达自己. 今天, ...
- vue.js - 1
最近开发公司vue前端项目,做一下笔记,偶尔上来查漏补缺 Vue指令之v-bind的三种用法 直接使用指令v-bind 使用简化指令: 在绑定的时候,拼接绑定内容::title="btnTi ...
- 基于tkinter的九型人格测试系统介绍
基于tkinter的九型人格测试系统介绍 一.程序代码地址,GitHub 二.程序介绍 1.login.py 登录界面: 注册界面: 2.mainWindow.py 登录成功之后的界面: 3.doTe ...
- Java Calender 类详解
一. 如何创建 Calendar 对象 Calendar 是一个抽象类, 无法通过直接实例化得到对象. 因此, Calendar 提供了一个方法 getInstance,来获得一个Calendar ...
- Linux链接脚本学习--lds(转)
Linux链接脚本学习--lds 一.概论 ld: GNU的链接器. 用来把一定量的目标文件跟档案文件链接在一起,并重新定位它们的数据,链接符号引用. 一般编译一个程序时,最后一步就是运行ld进行链接 ...
- Java - Tips
001 - Java中print.printf与println的区别? printf:格式化输出,用来控制输出的格式. print:标准输出,不换行. println:标准输出,换行.例如,print ...
- Java - 初识Java
Java是一种跨平台的面向对象的程序设计语言. 区分大小写: 标点符号为英文: 强类型语言,在Java中存储的数据,必须在编译时确定其类型: Java版本 Java按应用范围分为3个版本,即Java ...
- System.Threading.Tasks.Task 引起的 IIS 应用池崩溃
接口服务运行一段时间后,IIS应用池就会突然挂掉,事件查看日志,会有事件日志Event ID为5011的错误 为应用程序池“PokeIn”提供服务的进程在与 Windows Process Activ ...