这篇文章可以在知网下载,发表时间2020-06-07。以下是个人对这篇文章的阅读笔记和个人感想。
侵删。
背景和简介
通过摘要和介绍,可以了解到Spark的“统一内存管理”模型共分为4层,自上往下分别是Spark层、JVM层、OS层、Hardware层。目前对Spark的性能优化,只停留在某一层。
个人理解为,对Spark的优化,大多数是通过配置Spark集群的参数、重构代码更适合环境、或者优化JVM、或者配置内存等。都是通过调优某一层来优化Spark。
更完美的调优,应该是对于某一方向,将OS层、JVM层、Spark层都向其调优,当然三者之间应建立某种联系,达到一个平衡点。
本文是设计了一个访存行为的跨层分析工具SMTT,建立了上层应用程序的语义与底层物理内存信息的联系。
设计难点:
- 每个层都有自己的内存管理机制,需要不破坏其机制的同时,找到每个机制之间的内在联系;
- 现有对性能分析,都在某一层,即OS层的各个参数、或者JVM层的各个参数,建立联系比较困难;
- OS层还有别的性能指标,需要排除逻辑上不相关的参数;
- 管理内存一般采用虚拟地址,造成无法得知物理地址,无法追踪,进而分析。
设计理念
Spark层将内存分为保留内存、用户内存、Spark内存。保留内存存放Spark内部对象;用户内存由Spark应用程序使用;Spark内存存储应用程序的数据。其又分为执行内存和存储内存。
Spark内存分为执行内存和存储内存。执行内存一般用于shuffle开销,存储内存一般用于存储持久化RDD。SMTT针对两种内存设置了两种不同的追踪方案。
对于执行内存,将RDD和虚拟内存地址建立联系。对于存储内存,剥离其外部数据结构,对数据语义和虚拟地址建立联系。
SMTT分别对JVM堆内和堆外的数据进行处理,获得虚拟地址。对于堆内获取的内存找到对应的JVM对象,并将JVM对象转换为OS层的虚拟地址,对于堆外的内存,直接找到起始地址,即虚拟地址。最后再将虚拟地址转为物理地址,得到物理页号等信息。
得到的访问序列信息如下:
- 访问时间
- 访问类型
- Spark语义
- 虚拟地址
- 虚拟页信息
- 物理地址
- 物理页信息
最后实验,通过这些信息,分析得到Spark对于内存读写的负载情况。
执行内存追踪方案
- 在Task中,获取Writer的Hash码和RDD信息,将两者写入一张Hash表,得到Writer和RDD信息的关系;
- 在Writer中,将所用Sorter的Hash码和当前的Writer的Hash码写入张Hash表,这时候建立起RDD信息和Sorter之间的联系;
- 在Sorter中,将当前Sorter的Hash码,以及虚拟内存地址发送给SMTT,这时候建立起RDD信息和虚拟内存地址的联系;
- 根据OS页表,通过虚拟内存地址得到物理内存信息;
- 将RDD信息、虚拟地址、物理地址信息作为一条记录保存到文件。
存储内存追踪方案
MemoryStore对象提供了统一的存/取接口。内存维护了一个以Spark的BlockID对象为键、以MemoryEntry对象为值的Hash表。其中BlockID对象是RDDID和分区ID按一定格式的组合。MemoryEntry用于描述被存储的数据。其内部还有一个Java对象ByteBuffer数组,每一个ByteBuffer对象内部有一个存储数据的字节数组。
- 在MemoryStore对象内部,对Hash表进行存取,把BlockID对象和MemoryEntry对象给SMTT;
- 获取数据对象中的ByteBuffer数组
- 通过Unsafe对象获取字节数组在JVM的起始地址;
- 根据头信息长度获取数据的虚拟地址;
- 访问页表,虚拟地址转为物理地址
- 最后把各个信息写入文件
Spark计算过程追踪
这对于理解Spark的运行有很好的的帮助。首先应该知道的是,DAGscheduler会将job分为很多个阶段stage。
每个阶段的执行起点是当前阶段的最后一个RDD,这个RDD的每一个分区会交给一个Task线程。Task会调用最后一个RDD的iterator()方法获得其负责处理分区数据的迭代器,这个iterator()方法会调用当前RDD的computer()方法,递归下去调用到第一个RDD的compute()方法。第一个RDD的compute()方法会返回一个文本数据的迭代器,就是读取数据,返回给下一个RDD,下一个RDD会新创建一个迭代器对象Iter,并重写next()方法,递归返回给最后一个RDD。最终Task拿到迭代器,每调用一次next()方法,就从文件系统读取一条记录,并通过上述转换函数。
实验评测
本文以机器学习、SQL查询、图计算、流计算的应用,以读写RDD的评测结果为例,评测借助SMTT的追踪分析不同程序的不同特征的效果。
得到小结论:写开销略高于读开销,这是因为RDD被设计为只读的,RDD被持久化后可以重复读取,而写得不断开销。
最终结论:
- 各负载的RDD内存占用率差别较大,LR,SVM,SVDPP,TC内存占用率较高,PR,RR,PVS内存占用率较低,MF占用率最低。
- 相比单节点集群,多节点内存利用率较低。