【Spark学习笔记】Spark基本知识
前记
入职四个月了,面试的Java工程师,没想到工作是要做数据相关的开发,也是挺无奈。目前主要做Spark相关开发,经过一段时间的学习和使用,对Spark也算是较为熟悉了,故写个笔记整理下自己掌握的Spark知识。
一、Spark基础概念
1.Spark的特点
在Spark出现前,主流的分布式计算框架是MapReduce,Spark逐渐取代MapReduce主要在于其有以下两点优势。
1、更快的执行速度。这主要得益于Spark引入的RDD编程模型以及DAG调度程序。
2、简单易用的编程模型。这得益于Spark提供的丰富的算子操作。
2.Spark的相关名词
Spark 集群由集群管理器 Cluster Manager、工作节点 Worker、执行器 Executor、驱动器 Driver、应用程序 Application 等部分组成。
(1)Cluter Manager
负责集群的资源分配和管理。根据部署模式的不同,可以分为Spark自己standalone、Mesos和YARN(最常用)
(2)Worker
负责执行提交任务,在 YARN 部署模式下 Worker 由 NodeManager 代替。Work的作用如下:
a)通过注册机制向 Cluster Master 汇报自身的 cpu 和 memory 等资源
b)在 Master 的指示下创建启动 Executor
c)为Executor分配资源和任务
d)同步资源信息、Executor状态信息给 Cluster Master
(3)Executor
真正执行计算任务的组件。Executor 是某个 Application 运行在 Worker 节点上的一个进程,该进程负责运行某些 Task, 并且负责将数据存到内存或磁盘上,每个 Application 都有各自独立的一批 Executor。
(4)Driver
负责运行main函数并创建SparkContext。Driver是一种特殊的Executor进程,这个进程除了一般excutor都具有的运行环境外,其内部还运行着DAGscheduler Tasksheduler Schedulerbackedn等组件。
三、RDD
RDD的全称是resilient distributed dataset(分布式弹性数据集),其代表一个不可变、可分区、元素可并行操作的集合。
1.RDD属性
(1)不可变。每个RDD都是只读的,对RDD内元素处理后会得到一个新的RDD。
(2)可分区。每个RDD的数据被存储在系统的不同节点上,RDD只是抽象意义的数据集合,其内部不存储具体数据。
(3)可并行操作。由于RDD的数据是分区存储的,因此不同分区上的数据可以分别被处理,最后生成一个新的RDD。
2.RDD操作
RDD的操作主要分为转化操作(transformation)和行为操作(action)。
转换操作进行时,实际计算并未执行(惰性运算),只有当行动操作进行时,才会触发转换操作的执行。了解这两种操作的区别之后,下面对其中常用的算子分别进行具体介绍。
Transformation | Meaning |
- | :-: |
map(func) | 遍历rdd的每个元素,并对其进行相应的func操作,最终返回一个新的rdd |
filter(func) | 过滤rdd的每个元素,仅保留func返回值为true的元素,最终返回一个新的rdd|
flatMap(func) | 跟map算子类似, |
mapPartitions(func) | 跟map算子类似,但是mapPartitions在一次调用中会处理掉patition中的所有数据,性能高一点,但是可能会引发OOM问题,用的时候要注意。|
union(otherDataset) | 将源rdd和参数中的rdd进行合并,返回一个新的rdd |
distinct([numPartitions])) | 返回一个包含源rdd中所有不同元素的新rdd |
reduceByKey(func, [numPartitions]) | 对数据元素为键值对的rdd进行聚合操作,(K, V)=>(K, V),相同key的值由func:(V,V)=>V得到 |
groupByKey([numPartitions]) |对数据元素为键值对的rdd进行聚合操作,(K, V)=>(K, Iterable<V>),相同key的值放入同一序列(操作能用reduceByKey实现时尽量用reduceByKey,reduceByKey有预聚合操作性能更高) |
coalesce(numPartitions) | 将rdd中的patition数减少到指定数量,此操作在filter算子筛选掉大量数据后使用可以提升运行性能 |
repartition(numPartitions) | 对rdd中的数据进行随机重排列,以减少或增加patition的数量 |
Action | Meaning |
- | :-: |
collect() | 将rdd中的所有元素拉到driver program上,并以数组的形式返回。通常在经过过滤或其他操作使得rdd元素足够少后使用该算子会比较有用 |
count() | 返回rdd中元素的数量|
take(n) | 返回包含rdd中前n个元素的数组 |
saveAsTextFile(path) |将rdd数据作为文本写入给定的本地、hdfs或者hadoop支持的文件系统。Spark会对每个元素调用toString方法将其转换成文本的每一行 |
foreach(func) | 对rdd的每个元素执行一次回调函数func,通常使用在更新累加器或者与外部存储系统交互的时候 |
三、性能调优
掌握了Spark的常用算子之后便可以进行相应的数据开发了,但是开发过程中需要做合理的调优,否则计算大量数据时Spark作业可能会执行特别久。调优的相关知识主要通过美团技术团队的博客进行的学习,这里对博客的内容做个简单总结。(博客地址)
1.Spark基本开发原则
(1)避免创建重复的RDD
(2)尽可能复用同一个RDD
(3)对多次使用的RDD进行持久化
(4)尽量避免使用shuffle类算子
(5)使用map-side预聚合的shuffle操作
(6)使用高性能的算子:reduceByKey/aggregateByKey优于groupByKey、mapPartitions优于普通map、foreachPartitions优于foreach、repartitionAndSortWithinPartitions优于repartition与sort类操作
(7)广播大变量
(8)使用Kryo优化序列化性能
2.资源参数调优
(1)num-executors
该参数用于设置Spark作业总共要用多少个Executor进程来执行。每个Spark作业的运行一般设置50~100个左右的Executor进程比较合适。
(2)executor-memory
该参数用于设置每个Executor进程的内存。每个Executor进程的内存设置4G~8G较为合适,具体的设置还是得根据不同部门的资源队列来定。
(3)executor-cores
该参数用于设置每个Executor进程的CPU core数量。Executor的CPU core数量设置为2~4个较为合适。
(4)driver-memory
该参数用于设置Driver进程的内存。Driver的内存通常来说不设置,或者设置1G左右应该就够了。但是若需使用collect算子需调大Driver内存。
(5)spark.default.parallelism
该参数用于设置每个stage的默认task数量。设置该参数为num-executors * executor-cores的2~3倍较为合适。
(5)spark.storage.memoryFraction
该参数用于设置RDD持久化数据在Executor内存中能占的比例。如果Spark作业中,有较多的RDD持久化操作,该参数的值可以适当提高一些,保证持久化的数据能够容纳在内存中。
(6)spark.shuffle.memoryFraction
该参数用于设置shuffle过程中一个task拉取到上个stage的task的输出后,进行聚合操作时能够使用的Executor内存的比例。如果Spark作业中的RDD持久化操作较少,shuffle操作较多时,建议降低持久化操作的内存占比,提高shuffle操作的内存占比比例。