spark 基于分区操作 mapPartitions


基于分区对数据进行操作可以让我们避免为每个数据元素进行重复的配置工作。诸如打开数据库连接或者创建随机生成数等操作,都是我们应当避免的为每个元素都配置一遍的工作。Spark提供基于分区的map和foreach,让你的部分代码只对RDD的每个分区运行一次,这样可以帮助降低这些操作的代价。

当基于分区操作RDD时,Spark会为函数提供该分区中的元素的迭代器。返回值方面,也返回一个迭代器。

Spark 提供的基于分区操作的操作符,大概有:

mapPartitions()

mapPartitionsWithIndex()

foreachPartitions()

注意:foreachPartition 和 mapPartitions的分别,foreachPartition应该属于action运算操作,而mapPartitions是在Transformation中,所以是转化操作,此外在应用场景上区别是mapPartitions可以获取返回值,继续在返回RDD上做其他的操作,而foreachPartition因为没有返回值并且是action操作,所以使用它一般都是在程序末尾比如说要落地数据到存储系统中如mysql,es,或者hbase中,可以用它。当然在Transformation中也可以落地数据,但是它必须依赖action操作来触发它,因为Transformation操作是延迟执行的,如果没有任何action方法来触发,那么Transformation操作是不会被执行的,这一点需要注意。

下面分别举几个例子:

mapPartitionsWithIndex(), 和 mapPartitions() 类似,但是同时提供分区的索引

Demo

显示每个分区元素的分区索引
scala> val rdd1 =  sc.parallelize( List("yellow","red", "blue", "cyan", "black"), 3 )
rdd1: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[101] at parallelize at <console>:26
scala> val mapped =   
     | rdd1.mapPartitionsWithIndex{
     | (index, iterator) => {
     |  println("Called in Partition -> " + index)
     | val myList = iterator.toList
     | myList.map(x => x + " -> " + index).iterator
     | }
     | }
mapped: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[102] at mapPartitionsWithIndex at <console>:29

scala> mapped.collect()
Called in Partition -> 0
Called in Partition -> 1
Called in Partition -> 2
res48: Array[String] = Array(yellow -> 0, red -> 1, blue -> 1, cyan -> 2, black -> 2)

mapPartitions():

与map方法类似,map是对rdd中的每一个元素进行操作,而mapPartitions(foreachPartition)则是对rdd中的每个分区的迭代器进行操作。

SparkSql或DataFrame默认会对程序进行mapPartition的优化。

Demo

实现将每个数字变成原来的2倍的功能

比如:输入2,结果(2,4)

使用map

scala> val a = sc.parallelize(1 to 9, 3)
a: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[103] at parallelize at <console>:26

scala> def mapDoubleFunc(a : Int) : (Int,Int) = {
     |     (a,a*2)
     | }
mapDoubleFunc: (a: Int)(Int, Int)

scala> val mapResult = a.map(mapDoubleFunc)
mapResult: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[104] at map at <console>:30

scala> println(mapResult.collect().mkString)
(1,2)(2,4)(3,6)(4,8)(5,10)(6,12)(7,14)(8,16)(9,18)

使用mapPartitions


scala> val a = sc.parallelize(1 to 9, 3)
a: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[105] at parallelize at <console>:26

scala> def doubleFunc(iter: Iterator[Int]) : Iterator[(Int,Int)] = {
     |     var res = List[(Int,Int)]()
     |     while (iter.hasNext)
     |     {
     |       val cur = iter.next;
     |       res .::= (cur,cur*2)
     |     }
     |     res.iterator
     |   }
doubleFunc: (iter: Iterator[Int])Iterator[(Int, Int)]

scala> val result = a.mapPartitions(doubleFunc)
result: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[106] at mapPartitions at <console>:30

scala> println(result.collect().mkString)
(3,6)(2,4)(1,2)(6,12)(5,10)(4,8)(9,18)(8,16)(7,14)

再举一个例子:

Demo

求平均值

不使用mapPartitions()求平均值

import org.apache.spark.rdd.RDD
val nums=sc.parallelize(List(1,2,3,4),3)

//不使用mapPartitions()求平均值
def reduce(x:(Int,Int),y:(Int,Int)):(Int,Int)={(x._1+y._1,x._2+y._2)}

def basicAvg(nums:RDD[Int]):Float={
  val sum = nums.map(x=>(x,1)).reduce(reduce)
  return sum._1 / sum._2.asInstanceOf[Float]
}

结果:

scala> basicAvg(nums)
res90: Float = 2.5

使用mapPartitions()求平均值

//使用mapPartitions()求平均值
def meanFunc(iter: Iterator[Int]) : Iterator[(Int,Int)]= {
     var x = 0
     var y = 0
     var res = List[(Int,Int)]()
     while (iter.hasNext)
     {
     val cur = iter.next
     x = x+cur
     y = y+ 1 
     }
     res .::= (x,y)
     res.iterator
}

//计算平均值
//如果没有import RDD,则需使用 org.apache.spark.rdd.RDD[Int]
def fastAvg(nums:RDD[Int]):Float={
	val sumCount = nums.mapPartitions(meanFunc).reduce(reduce)
  return sumCount._1 / sumCount._2.asInstanceOf[Float]
}

结果:

scala> fastAvg(nums)
res91: Float = 2.5

最后,需要注意一点,如果操作是iterator类型,我们是不能在循环外打印这个iterator的size,一旦执行size方法,相当于iterato就会被执行,所以后续的foreach你会发现是空值的,切记iterator迭代器只能被执行一次。




参考资料:

https://blog.csdn.net/u010454030/article/details/78897150

https://blog.csdn.net/wusuopubupt/article/details/53258067

https://blog.csdn.net/lsshlsw/article/details/48627737






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值