Spark从入门到精通六------RDD的算子

 

版权声明:本文为博主原创文章,未经博主允许不得转载!

欢迎访问:https://blog.csdn.net/qq_21439395/article/details/83591271

交流QQ: 824203453

  1. RDD编程API
    1. RDD算子

算子是RDD中定义的方法,分为转换(transformantion)和动作(action)。Tranformation算子并不会触发Spark提交作业,直至Action算子才提交任务执行,这是一个延迟计算的设计技巧,可以避免内存过快被中间计算占满,从而提高内存的利用率。

RDD拥有的操作比MR丰富的多,不仅仅包括Map、Reduce操作,还包括filter、sort、join、save、count等操作,并且中间结果不需要保存,所以Spark比MR更容易方便完成更复杂的任务。

 

RDD支持两种类型的操作:

转换(Transformation)  现有的RDD通过转换生成一个新的RDD。lazy模式,延迟执行。

转换函数包括:map,filter,flatMap,groupByKey,reduceByKey,aggregateByKey,union,join, coalesce等等。

动作(Action)  在RDD上运行计算,并返回结果给驱动程序(Driver)或写入文件系统。

动作操作包括:reduce,collect,count,first,take,countByKey以及foreach等等。

collect  该方法把数据收集到driver端   Array数组类型

所有的transformation只有遇到action才能被执行。

当触发执行action之后,数据类型不再是rdd了,数据就会存储到指定文件系统中,或者直接打印结果或者收集起来。

 

RDD操作流程示意:

RDD的转换与操作

 

wordcount示例,查看lazy特性。

只有在执行action时,才会真正开始运算,并得到结果或存入文件中。

      1. Transformation

RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行。

对RDD中的元素执行的操作,实际上就是对RDD中的每一个分区的数据进行操作,不需要关注数据在哪个分区中。

常用的Transformation:

转换

含义

map(func)

返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成

filter(func)

返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成

flatMap(func)

先map,再flatten压平

union(otherDataset)

对源RDD和参数RDD求并集后返回一个新的RDD

intersection(otherDataset)

对源RDD和参数RDD求交集后返回一个新的RDD

subtract(otherDataset)

求差集后返回新的RDD,出现在源rdd中,不在otherrdd中

distinct([numTasks]))

对源RDD进行去重后返回一个新的RDD

mapPartitions(func)

类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]

mapPartitionsWithIndex(func)

类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是

(Int, Interator[T]) => Iterator[U]

sortBy(func,[ascending], [numTasks])

与sortByKey类似,但是更灵活

sortByKey([ascending], [numTasks])

在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD

join(otherDataset, [numTasks])

在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD

cogroup(otherDataset, [numTasks])

在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD

cartesian(otherDataset)

笛卡尔积

mapValues(func)

在一个(K,V)的RDD上调用

groupBy (func, [numTasks])

根据自定义条件进行分组

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])

针对分区内部使用seqOp方法,针对最后的结果使用combOp方法。

coalesce(numPartitions)      

用于对RDD进行重新分区,第一个参数是分区的数量,第二个参数是是否进行shuffle,可不传,默认不shuffle

repartition(numPartitions)

用于对RDD进行重新分区,相当于shuffle版的calesce

 

groupBy的返回值类型:

def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]

T: 元素的类型   K : 指定的key

 

groupByKey

def groupByKey(): RDD[(K, Iterable[V])]

 

reduceByKey

 

优先选择reduceByKey,  语法更简洁

性能优越

reduceByKey会进行分区内聚合,再经过网络传输,发送到相对应的分区中。

 

 

sortBy既可以作用于RDD[K] ,还可以作用于RDD[(k,v)]

sortByKey  只能作用于 RDD[K,V] 类型上。

 

      1. Action

动作

含义

reduce(func)

通过func函数聚集RDD中的所有元素

collect()

在驱动程序中,以数组的形式返回数据集的所有元素

collectAsMap

类似于collect。该函数用于Pair RDD,最终返回Map类型的结果。

count()

返回RDD的元素个数

first()

返回RDD的第一个元素(类似于take(1))

take(n)

返回一个由数据集的前n个元素组成的数组

saveAsTextFile(path)

 

将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统

top(n)

按照默认排序(降序) 取数据

takeOrdered(n[ordering])

与top类似,顺序相反  默认是升序

countByKey()

针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。

foreach(func)

在数据集的每一个元素上,运行函数func进行更新。foreach,任务在executor中运行,打印信息也会在executor中显示

foreachPartition

对分区进行操作

 

 

foreach和foreachPartition

foreachParititon 每次迭代一个分区,foreach每次迭代一个元素。

该方法没有返回值,或者Unit

主要作用于,没有返回值类型的操作(打印结果,写入到mysql数据库中)

在写入到mysql数据库的时候,优先使用foreachPartititon
* 结果 存入到 mysql
* foreachPartition
* 1,map mapPartition  
转换类的算子, 返回值
* 2, mysql  数据库的连接
* 100万         100万次的连接
* 200 个分区     200次连接  一个分区中的数据,共用一个连接

 

 

foreach和map的区别:

map有返回值,foreach没有返回值(Unit类型)

map是transformation,lazy执行,foreach是action算子,触发任务运行

处理的都是每一条数据。

 

 

rdd1.foreach(println)
rdd1.foreachPartition(it=>println(it.mkString(
"")))

 

 

 

coalesce和repartition

def coalesce(numPartitions: Int, shuffle: Boolean = false)(implicit ord: Ordering[T] = null): RDD[T]

coalesce(n) 原来的分区中的数据,不会被分配到多个分区中,

将RDD分区的数量修改为numPartitions,常用于减少分区

第一个参数为重分区的数目,第二个为是否进行shuffle,默认为false

当需要调大分区时,必须设置shuffle为true,才能有效,否则分区数不变

 

 

随机重新shuffle RDD中的数据,并创建numPartitions个分区。这个操作总会通过网络来shuffle全部数据。常用于扩大分区

分区数调大调小,都会shuffle全部数据,是重量级算子

 

常用用法?

coalesce(10,true) = reparititon(10)

如果不需要数据的shuffle,减少或者合并分区,就使用coalesce(num)

如果需要数据的shuffle,或者需要扩大分区数量,优先使用repartition(num)

扩大分区,作用:提升并行度(业务逻辑比较复杂,需要提升并行度)

 

// 重分区的api
   
rdd1.coalesce(2)     // coalesce(numPartitions: Int, shuffle: Boolean = false
   
rdd1.repartition(2// coalesce(numPartitions, shuffle = true)
//    repartition
就是coalesce,第二个参数为true
    /**  repartition
要进行数据的shuffle   不管是扩大分区,还是减少分区,都进行shuffle
      *  coalesce
默认没有进行数据的shuffle   减少分区,直接是分区合并。
     
*  coalesce   扩大分区,
       */

 

val f = (i:Int,it:Iterator[Int]) =>

     it.map(t=> s"part:$i,values:$t")

 

另外还有一类可以修改分区的方式:

在调用shuffle类的算子时,可以在参数中设置分区的数量:

def reduceByKey(func: (V, V) => V, numPartitions: Int):

 

mapPartitions和mapPartitionsWithIndex

def mapPartitions[U](f: (Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]

该函数和map函数类似,只不过映射函数的参数由RDD中的每一个元素变成了RDD中每一个分区的迭代器。

如果在映射的过程中需要频繁创建额外的对象,使用mapPartitions要比map高效。

该方法,看上去是操作的每一条数据,实际上是对RDD中的每一个分区进行iterator,

 

mapPartitions( it: Iterator => {it.map(x => x * 10)})

 

mapPartitionsWithIndex

def mapPartitionsWithIndex[U](f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]

类似于mapPartitions, 不过提供了两个参数,第一个参数为分区的索引。

mapPartitionsWithIndex的func接受两个参数,第一个参数是分区的索引,第二个是一个数据集分区的迭代器。而输出的是一个包含经过该函数转换的迭代器。

 

val func = (index: Int, iter: Iterator[Int]) => {

  iter.map(x => "[partID:" +  index + ", val: " + x + "]")

}

val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)

rdd1.mapPartitionsWithIndex(func).collect

/**
      * map
      * mapValues
      * mapPartition     
操作的是每一个分区    函数的输入参数类型是Iterator[元素类型]
      * mapPartitionWithIndex
      *
      */

    //
创建rdd  同时指定分区的数量为2个  数据会被打散,然后平均分配
   
val rdd = sc.makeRDD(List(1, 3, 5, 7, 9), 2) // part 0 1

   
rdd.map({
      i =>
       
// 具体的元素
       
i * 10
   
})
   
// 该方法每次操作的对象是一个迭代器,对应的就是一个分区的数据
    
rdd.mapPartitions({
      it =>
       
// 分区
       
it.map(_ * 10)
    })

   
// 函数
   
val f=(index:Int,it:Iterator[Int])=>{
      it.map({
        t=>
s"part:$index,value=$t"
     
})
    }

   
val index1: RDD[String] = rdd.mapPartitionsWithIndex({
     
// 第一个参数,是分区的索引  分区编号
     
// 第二个参数: 分区的数据 Iterator[Int]
     
case (index, it) =>
        it.map({
          t =>
           
s"part:$index,value=$t"
       
})
    })

//    ArrayBuffer(part:0,value=1, part:0,value=3, part:1,value=5, part:1,value=7, part:1,value=9)
    //
收集数据并打印
   
println(index1.collect().toBuffer)

 

 

collect方法:

 

不能直接把数据收集到driver段,然后再执行入库操作。

效率太低,容易引起dirver端崩溃了。OOM

 

版权声明:本文为博主原创文章,未经博主允许不得转载!

欢迎访问:https://blog.csdn.net/qq_21439395/article/details/83591271

交流QQ: 824203453

 

猜你喜欢

转载自blog.csdn.net/qq_21439395/article/details/83591271