目录
Spark是专为大规模数据处理而设计的快速通用的计算引擎;拥有Hadoop MapReduce所具有的优点,但是运行速度却比MapReduce有很大的提升。
Spark是基于内存进行数据处理的,MapReduce是基于磁盘进行数据处理的。
什么是算子
RDD的方法和Scala集合对象的方法不一样,集合对象的方法都是在同一个节点的内存中完成的。RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行。
为了区分不同的处理效果,所以将RDD的方法称为算子,RDD的方法外部的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行。
Spark算子大致可以分为4类:创建算子、转换算子、缓存算子、行动算子
1.Transformation 转换算子
1.1 转换算子
不触发提交作业,只是完成作业中间过程处理;Transformation 操作是延迟计算的,也就是说从一个RDD 转换生成另一个 RDD 的转换操作不是马上执行,需要等到有 Action 操作的时候才会真正触发运算。Transformation参数类型为value或者key-value的形式;
转换算子是延迟执行的,也叫懒加载执行
1.2 转换算子是干什么的
就是对RDD进行操作的接口函数,其作用是将一个或多个RDD变换成新的RDD。
Spark中,在利用创建算子生成RDD后,数据处理的算法设计和程序编写的最关键部分,就是利用变换算子对原始数据产生的RDD进行一步一步的变换,最终得到期望的计算结果。
1.3 转换算子分类
可分为两类:
-
对Value型RDD进行变换的算子;
-
对Key/Value型RDD进行变换算子。在每个变换中有仅对一个RDD进行变换的,也有是对两个RDD进行变换的。
1.4 转换算子详解
1.4.1 map
将一个RDD中的每个数据项,通过map中的函数映射变为一个新的元素。 输入分区与输出分区一对一,即:有多少个输入分区,就有多少个输出分区,分区数不会改变。
scala> val a = sc.parallelize(List("dog","salmon","salmon","rat","elephant"),3) a: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallelize at <console>:24 scala> a.partitions.size res0: Int = 3 // glom的作用是将同一个分区里的元素合并到一个array里 // glom属于Transformation算子:这种变换并不触发提交作业,完成作业中间过程处理。 scala> a.glom.collect res2: Array[Array[String]] = Array(Array(dog), Array(salmon, salmon), Array(rat, elephant)) scala> a.map(_.length).glom.collect res4: Array[Array[Int]] = Array(Array(3), Array(6, 6), Array(3, 8))
1.4.2 flatMap
同Map算子一样,最后将所有元素放到同一集合中,分区数不会改变。注意:针对Array[String]类型,将String对象视为字符数组。将原来 RDD 中的每个元素通过函数 f 转换为新的元素,并将生成的 RDD 的每个集合中的元素合并为一个集合。
lines.flatMap{lines => { lines.split(" ")
1.4.3 distinct
将RDD中重复元素做去重处理。
scala> a.distinct.collect res22: Array[String] = Array(rat, salmon, elephant, dog)
1.4.4 mapPartitions
mapPartitions 函 数 获 取 到 每 个 分 区 的 迭 代器,在 函 数 中 通 过 这 个 分 区 整 体 的 迭 代 器 对整 个 分 区 的 元 素 进 行 操 作。 内 部 实 现 是 生 成 -------f (iter)=>iter.f ilter(_>=3)。
1.4.5 glom
glom函数将每个分区形成一个数组,内部实现是返回的GlommedRDD。 下图中的每个方框代表一个RDD分区。 该图表示含有V1、 V2、 V3的分区通过函数glom形成一数组Array[(V1),(V2),(V3)]。
1.4.6 union
使用 union 函数时需要保证两个 RDD 元素的数据类型相同,返回的 RDD 数据类型和被合并的 RDD 元素数据类型相同,并不进行去重操作,保存所有元素。如果想去重可以使用 distinct()(并集)。
1.4.7 cartesian
对 两 个 RDD 内 的 所 有 元 素 进 行 笛 卡 尔 积 操 作。 操 作 后, 内 部 实 现 返 回CartesianRDD。
1.4.8 groupBy
将元素通过函数生成相应的 Key,数据就转化为 Key-Value 格式,之后将 Key 相同的元素分为一组。
1.4.9 subtract
subtract相当于进行集合的差操作,RDD 1去除RDD 1和RDD 2交集中的所有元素。
1.4.10 sample
sample 将 RDD 这个集合内的元素进行采样,获取所有元素的子集。用户可以设定是否有放回的抽样、百分比、随机种子,进而决定采样方式。内部实现是生成 SampledRDD(withReplacement, fraction, seed)。
1.4.11 mapValues
针对(key, Value)型数据中的value进行map操作,而不对key进行处理。
1.4.12 reduceByKey
reduceByKey的作用对像是 (key, value)形式的rdd,而reduce有减少、压缩之意,reduceByKey的作用就是对相同key的数据进行处理,最终每个key只保留一条记录。. 保留一条记录通常有两种结果。
1.4.13 partitionBy
partitionBy函数对RDD进行分区操作;
partitionBy(partitioner:Partitioner)。
1.4.14 join
join 对两个需要连接的 RDD 进行 cogroup函数操作,将相同 key 的数据能够放到一个分区,在 cogroup 操作之后形成的新 RDD 对每个key 下的元素进行笛卡尔积的操作,返回的结果再展平,对应 key 下的所有元组形成一个集合。最后返回 RDD[(K, (V, W))]。
1.4.15 sortyByKey(sortBy)
作用再(Key, Value)格式的数据上,根据Key进行升序或降序排序。
2.Action
Action行动算子。
本质上在Action算子中通过SparkContext触发SparkContext提交job作业。Action 算子会触发 Spark 提交作业(Job),并将数据输出 Spark系统。
2.1 foreach
foreach对RDD中的每个元素都应用f函数操作,不返回RDD和Array,而返回Uint.也就是遍历。
2.2 saveAsTextFile
函数将数据输出,存储到HDFS的制定目录下。
2.3 saveAsObjectFile
将分区中的每10个元素组成一个Array,然后将这个Array序列化,映射为Null,BytesWritable(Y)的元素,写入HDFS为SequenceFile的格式;
2.4 colloect
相当于toArray,collect将分布式的RDD返回为一个单机的scala Array数组,在这个数组上运用scala的函数式操作;
左侧方框代表 RDD 分区,右侧方框代表单机内存中的数组。通过函数操作,将结果返回到 Driver 程序所在的节点,以数组形式存储。
2.5 collectAsMap
collectAsMap对(K,V)型的RDD数据返回一个单机HashMap; 对于重复K的RDD元素,后面的元素覆盖前面的元素
2.6 lookup
Lookup函数对(Key,Value)型的RDD操作,返回指定Key对应的元素形成的Seq。
这个函数处理优化的部分在于,如果这个RDD包含分区器,则只会对应处理K所在的分区,然后返回由(K,V)形成的Seq。 如果RDD不包含分区器,则需要对全RDD元素进行暴力扫描处理,搜索指定K对应的元素。
lookup(key:K):Seq[V]
2.7 count
count(计数器)返回整个RDD的元素个数。
2.8 top
top可返回最大的k个元素
-
top返回最大的k个元素。
-
take返回最小的k个元素。
-
takeOrdered返回最小的k个元素,并且在返回的数组中保持元素的顺序。
-
first相当于top(1)返回整个RDD中的前k个元素,可以定义排序的方式Ordering[T],返回的是一个含前k个元素的数组.
2.9 reduce
reduce函数相当于对RDD中的元素进行reduceLeft函数的操作;
reduceLeft先对两个元素<K,V>进行reduce函数操作,然后将结果和迭代器取出的下一个元素<k,V>进行reduce函数操作,直到迭代器遍历完所有元素,得到最后结果。在RDD中,先对每个分区中的所有元素<K,V>的集合分别进行reduceLeft。 每个分区形成的结果相当于一个元素<K,V>,再对这个结果集合进行reduceleft操作;
2.10 fold
fold和reduce的原理相同,但是与reduce不同,相当于每个reduce时,迭代器取的第一个元素是zeroValue。
3.对应分区
1.转换算子
1.1 value 类型
细类型 | 算子 |
---|---|
输入分区与输出分区一对一型 | map flatMap mapPartitions glom |
输入分区与输出分区多对一型 | union cartesain |
输入分区与输出分区多对多型 | groupBy |
输出分区为输入分区子集型 | filter distinct substract sample takeSample |
Cache型 | cache persist |
1.2 key-value类型
细类型 | 算子 |
---|---|
输入分区与输出分区一对一 | mapValues |
对单个RDD或两个RDD聚集 | 单个RDD聚集: combineByKey reduceByKey partitionBy; 两个RDD聚集: Cogroup |
连接 | join leftOutJoin和 rightOutJoin |
2.行动算子
细类型 | 算子 |
无输出 | foreach |
HDFS | saveAsTextFile saveAsObjectFile |
Scala集合和数据类型 | collect collectAsMap reduceByKeyLocally lookup count top reduce fold aggregate |
4.Spark流程
1.创建SparkConf对象
- 可以设置Application name;
- 可以设置运行模式及资源需求;
2.创建SparkContext对象;
- SparkContext向资源管理器申请运行Executor资源,并启动StandaloneExecutorbackend;
- Executor向SparkContext申请Task;
- SparkContext将程序分发给Executor;
- SparkContext构建成DAG图,将DAG图分解成Stage、将Taskset发送给Task Scheduler,最后由Task Scheduler将Task发送给Executor运行;
- Task在Excutor上运行,运行完释放所有的资源;
3.创RDD
基于Spark的上下文创建一个RDD,对RDD进行处理;
4.触发执行
应用程序中y有Action累算子来触发Transformation类算子执行;
5.关闭程序
关闭Spark上下文对象SparkContext;
5 spark分区
Spark RDD 是一种分布式的数据集,由于数据量很大,因此要它被切分并存储在各个结点的分区当中。从而当我们对RDD进行操作时,实际上是对每个分区中的数据并行操作。
每一个过程的任务数,对应一个inputSplit1, Partition输入可能以多个文件的形式存储在HDFS上,,每个File都包含了很多块,(128M切分),称为Block。
当Spark读取这些文件作为输入时,会根据具体数据格式对应的InputFormat进行解析,一般是将若干个Block合并成一个输入分片,称为InputSplit,注意InputSplit不能跨越文件。
随后将为这些输入分片生成具体的Task。InputSplit与Task是一一对应的关系。然后这些具体的Task每个都会被分配到集群上的某个节点的某个Executor去执行。
- 每个节点可以起一个或多个Executor。
- 每个Executor由若干core组成,每个Executor的每个core一次只能执行一个Task。
- 每个Task执行的结果就是生成了目标RDD的一个partiton。
1、partition的数目设置:
对于数据读入阶段,例如sc.textFile,输入文件被划分为多少InputSplit就会需要多少初始Task。在Map阶段partition数目保持不变。
在Reduce阶段,RDD的聚合会触发shuffle操作,聚合后的RDD的partition数目跟具体操作有关,例如repartition操作会聚合成指定分区数,还有一些算子是可配置的。
RDD在计算的时候,每个分区都会起一个task,所以rdd的分区数目决定了总的task数目。申请的计算节点(Executor)数目和每个计算节点核数,决定了你同一时刻可以并行执行的task。
比如:RDD有100个分区,那么计算的时候就会生成100个task,设置task间并行的参数是conf spark.sql.shuffle.partitions=100
你的资源配置为10个计算节点,(执行器excutor) --num-executors 10 默认为2一般设置在50-100之间,每个2个核,executor-cores 2 一般 2~4 为宜。同一时刻可以并行的task数目为20,计算这个RDD就需要5个轮次。Task被执行的并发度 = Executor数目 * 每个Executor核数(=core总个数)
如果计算资源不变,你有101个task的话,就需要6个轮次,在最后一轮中,只有一个task在执行,其余核都在空转。
如果资源不变,你的RDD只有2个分区,那么同一时刻只有2个task运行,其余18个核空转,造成资源浪费。
这就是在spark调优中,增大RDD分区数目,增大任务并行度的原因。
2、spark分区 什么时候增加的,增加有什么用?
接下来的描述,是针对于sparksql(也就是把数据加载成Dataset之后再处理)来说的。
1.增加分区数,可以增加并行度,当spark申请的cpu核心足够的情况下,可以同时跑不同分区的数据(因为一个分区的数据,只能由一个核心来跑,不能多个)
2.手动增加,使用repartition来将所有数据打散
3.自动增加
spark有个参数:spark.sql.shuffle.partitions,默认值为200。也就是说当触发shuffle逻辑的时候,数据会自动分为200个分区运行,但是在数据量大的情况下,每个分区的数据量太大,而且假设spark申请到了300个核心,但是因为分区数只有200,会导致只有200个核心在运行,另外100个核心在空转(虽然占用资源但是却不干活)。所以可以将该参数设置为500甚至更大,来增加分区数和并行度。