今天也要努力学习
map与mapPartitions
两者的主要区别是作用对象不一样:map的输入变换函数是应用于RDD中每个元素,而mapPartitions的输入函数是应用于每个分区。
假设一个rdd有10个元素,分成3个分区。如果使用map方法,map中的输入函数会被调用10次;而使用mapPartitions方法的话,其输入函数会只会被调用3次,每个分区调用1次。
场景举例:大数据集情况下的资源初始化开销和批处理处理,当方法需要初始化一个耗时的资源,然后使用,比如数据库连接。这个时候map需要根据rdd中的元素进行多次的数据库 的连接和关闭,这样就耗费了很多不必要的资源,而mapPartitions只需要针对分区进行资源的初始化连接,显然在大数据集情况下(数据集中元素个数远大于分区数),mapPartitons的开销要小很多,也便于进行批处理操作。
mapPartiton的优势:
提高性能,比如我们对一个含有100条log数据的分区进行操作,使用map的话函数要执行100次计算。使用MapPartitions操作之后,一个task仅仅会执行一次function,function一次接收所有的partition数据。如果map执行的过程中还需要创建对象,比如创建jdbc连接等。map需要为每个元素创建一个链接而mapPartition为每个partition创建一个链接。
mapPartiton的缺点:
对于一个partition有很多数据的话,一次函数处理可能会导致OOM。普通的map一般不会导致OOM。
举个虚拟场景操作的例子:
package com.wuyue.spark
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
/**
* 测试mappartiton 和 foreachpartition 根据分区处理数据
*/
object mapPartition_foreachpartition {
def main(args: Array[String]): Unit = {
val session = SparkSession.builder()
.appName("wuyue")
.master("local")
.getOrCreate()
val sc = session.sparkContext
sc.setLogLevel("Error")
val rdd1: RDD[String] = sc.parallelize(List[String]("1","11","111","2","22","222"),2)
//这样的方式处理数据需要多次进行数据库的连接关闭,耗费资源
rdd1.map(s=>{
println("创建数据库连接。。。。。。。")
println("数据库操作。。。。。。。"+s)
println("关闭数据库连接。。。。。。。")
}).count() //用一个count来触发这个懒函数的执行
/**
* mappartition 传进去的是一个iterrator 传出来的也是一个iterator
* 传进来的iter是根据分区进行遍历 一次分区创建一次对象
* 一个分区传进一个迭代器。
*/
rdd1.mapPartitions(iter=>{
var list = ArrayBuffer[String]()
println("创建数据库连接。。。。。。。。")
while (iter.hasNext){
val curr = iter.next()
list.append(curr)
println(s"$curr 数据库操作。。。。。")
}
println("关闭数据库连接=====")
list.iterator
}).count()
}
}
map运行结果:
mapPartitions运行结果