一、各种算子的使用
转换 | 含义 |
---|---|
map(func) | 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 |
filter(func) | 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U] |
sample(withReplacement, fraction, seed) | 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) | 对源RDD和参数RDD求并集后返回一个新的RDD |
intersection(otherDataset) | 对源RDD和参数RDD求交集后返回一个新的RDD |
distinct([numTasks])) | 对源RDD进行去重后返回一个新的RDD |
groupByKey([numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD |
reduceByKey(func, [numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 |
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | 相同的Key值进行聚合操作,在聚合过程中同样使用了一个中立的初始值zeroValue:中立值,定义返回value的类型,并参与运算seqOp:用来在同一个partition中合并值combOp:用来在不同partiton中合并值 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) | 与sortByKey类似,但是更灵活 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
cartesian(otherDataset) | 笛卡尔积 |
pipe(command, [envVars]) | 将一些shell命令用于Spark中生成新的RDD |
coalesce(numPartitions**)** | 重新分区 |
repartition(numPartitions) | 重新分区 |
repartitionAndSortWithinPartitions(partitioner) | 重新分区和排序 |
3.25.18 Action算子
在RDD上运行计算,并返回结果给Driver或写入文件系统
动作 | 含义 |
---|---|
reduce(func) | 通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的 |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 |
count() | 返回RDD的元素个数 |
first() | 返回RDD的第一个元素(类似于take(1)) |
take(n) | 返回一个由数据集的前n个元素组成的数组 |
takeSample(withReplacement,num, [seed]) | 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子 |
takeOrdered(n, [ordering]) | takeOrdered和top类似,只不过以和top相反的顺序返回元素 |
saveAsTextFile(path) | 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) | 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 |
saveAsObjectFile(path) | |
countByKey() | 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。 |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新。 |
二、RDD的持久化
cache、persist、checkpoint
2.1 RDD的持久化(缓存)
cache和persist都是用于将一个RDD进行缓存。在该数据上的其他 action 操作将直接使用内存中的数据,这样在之后使用的过程中就不需要重新计算了,可以大大节省程序运行时间。
cache()方法表示:使用非序列化的方式将RDD中的数据全部尝试持久化到内存中。
persist()方法表示:手动选择持久化级别,并使用指定的方式进行持久化。
RDD可以使用persist()方法或cache方法进行持久化。数据将会在第一次action操作时进行计算,并缓存在节点的内存中。下一次的action直接调用这个缓存。
在 shuffle 操作中(例如 reduceByKey),即便是用户没有调用 persist 方法,Spark 也会自动缓存部分中间数据。这么做的目的是,在 shuffle 的过程中某个节点运行失败时,不需要重新计算所有的输入数据。
几种存储级别
MEMORY_ONLY
: 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够, 部分数据分区将不再缓存,在每次需要用到这些数据时重新进行计算。这是默认的级别。
MEMORY_AND_DISK
: 将 RDD 以反序列化 Java 对象的形式存储在 JVM 中。如果内存空间不够,将未缓存的数据分区存储到磁盘,在需要使用这些分区时从磁盘读取。
MEMORY_ONLY_SER
: 将 RDD 以序列化的 Java 对象的形式进行存储(每个分区为一个 byte 数组)。这种方式会比反序列化对象的方式节省很多空间,尤其是在使用 fast serializer时会节省更多的空间,但是在读取时会增加 CPU 的计算负担。
MEMORY_AND_DISK_SER
: 类似于 MEMORY_ONLY_SER ,但是溢出的分区会存储到磁盘,而不是在用到它们时重新计算。
DISK_ONLY
: 只在磁盘上缓存 RDD。
MEMORY_ONLY_2,MEMORY_AND_DISK_2,等等
: 与上面的级别功能相同,只不过每个分区在集群中两个节点上建立副本。
OFF_HEAP
: 类似于 MEMORY_ONLY_SER ,但是将数据存储在 off-heap memory,这需要启动 off-heap 内存。
通常不建议使用DISK_ONLY和后缀为_2的级别:
因为完全基于磁盘文件进行数据的读写,会导致性能急剧降低,有时还不如重新计算一次所有RDD。
后缀为_2的级别,必须将所有数据都复制一份副本,并发送到其他节点上,数据复制以及网络传输会导致较大的性能开销,除非是要求作业的高可用性,否则不建议使用。
参考:https://blog.csdn.net/u010003835/article/details/83271427
存储级别的参数:
1、_useDisk
:使用磁盘
2、_useMemory
:使用内存
3、_useOffHeap
:使用堆外存,这是Java虚拟机里面的概念,堆外内存意味着把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机)。这样做的结果就是能保持一个较小的堆,以减少垃圾收集对应用的影响。
4、_deserialized
:使用反序列化,其逆过程序列化(Serialization)是java提供的一种机制,将对象表示成一连串的字节;而反序列化就表示将字节恢复为对象的过程。序列化是对象永久化的一种机制,可以将对象及其属性保存起来,并能在反序列化后直接恢复这个对象
5、_replication
:副本数,默认是一个
作用:可以对公共代码块进行持久化,复用代码,提高效率,同时持久化机制,一定要触发Action算子,才会有效(数据将会在第一次 action 操作时进行计算,并缓存在节点的内存中)
cache和persist主要在于使用过程中,如果内存充足,可以直接使用cache,反之要选择其他级别的话,那么用persist进行级别更换,推荐使用内存+磁盘
看源码:
通过源码可以看出cache()是persist()的简化方式,调用persist的无参版本,也就是调用persist(StorageLevel.MEMORY_ONLY),cache只有一个默认的缓存级别MEMORY_ONLY,即将数据持久化到内存中,而persist可以通过传递一个 StorageLevel 对象来设置缓存的存储级别。
如果持久化使用结束,程序依然运行,那么需要释放掉缓存,节省空间,用 unpersist()
注意:cache()和persist()的使用是有规则的:
必须在transformation或者textfile等创建一个rdd之后,直接连续调用cache()或者persist()才可以,如果先创建一个rdd,再单独另起一行执行cache()或者persist(),是没有用的,而且会报错,大量的文件会丢失。
2.2 RDD的持久化(检查点)
检查点(本质是通过将RDD写入Disk做检查点) 是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。
//设置检查点
rdd.checkpoint
当使用了checkpoint后,数据被保存到HDFS,此RDD的依赖关系也会丢掉,因为数据已经持久化到HDFS,所以不需要重新计算,无论是cache或checkpoint缓存了数据,若数据丢失是自动恢复的,checkpoint的数据需要手动清除而cache的数据是自动清除的(任务结束)
由于是将RDD写入磁盘,因此非常占用资源,不建议经常使用。
什么时候使用checkpoint
1.某步骤计算特别耗时
2.计算链条特别长
3.发生shuffle之后
建议使用cache或是persist模式因为,不需要创建存储位置,并且默认存储到内存中计算速度快,而checkpoint需要手动创建存储位置和手动删除数据.若数据量非常庞大建议改用chechpoint.
三、RDD的共享变量
- 广播变量(Broadcast)
//将字典文件广播到各个节点的executor
private val board: Broadcast[Array[(String, String, String)]] =
sc.broadcast(dict.collect())
private val value: RDD[(String, Int)] = ip.map(x => {
val longIp = ip2Long(x)
//获取广播变量的值
val arrInfo: Array[(String, String, String)] = board.value
val i: String = binarySearch(arrInfo, longIp)
(i, 1)
})
广播变量可以将Driver创建的变量广播到每个节点的executor中,作为缓存使用,然后每个Task不需要每次都去拉取Driver的变量,这样减少的大量网络IO和磁盘IO,节省时间,减低资源消耗,同时减少每个Task的冗余性,提升效率
释放广播变量 unpersist
使用场景:比如在本地有字典文件,需要读取,并且不是太大,那么此时可以使用广播变量将其广播到executor内存中,供给每个Task进行使用,减少数据冗余性,提高效率