spark封神之路(9)-RDD算子详解第三部分

RDD 是spark抽象出来的运算逻辑,RDD中是不存储数据的,只记录数据的操作和RDD之间的血缘关系,只有执行到行动算子的时候才会处理真正的数据!

1.1 reduce

reduce将RDD中元素两两传递给输入函数,同时产生一个新值,新值与RDD中下一个元素再被传递给输入函数,直到最后只有一个值为止。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  val arr = Array(1,2,3,4,5,6,7,8,9)
  val rdd: RDD[Int] = sc.parallelize(arr ,3)
  //数据已经处理完成  得到最终的结果
  val i: Int = rdd.reduce((x1, x2) => x1 + x2)
  // 所以调用完算子返回的就是结果 直接打印
  println(rdd.reduce((x1, x2) => x1 + x2))
  sc.stop()
}

区内运算和分区间运算

1.2 fold

当分区内运算和分区间运算逻辑一样的时候可以使用foild算子

fold和reduce的原理相同,但是与reduce不同,相当于每个reduce时,迭代器取的第一个元素是zeroValue。
fold((”V0@”,2))((A,B)=>(A._1+”@”+B._1,A._2+B._2)))

val arr = Array(1,2,3,4)
val rdd: RDD[Int] = sc.parallelize(arr,4)
val res: Int = rdd.fold(10)(_+_)  // 60

1.3 aggregate

aggregate先对每个分区的所有元素进行aggregate操作,再对分区的结果进行fold操作。
  aggreagate与fold和reduce的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,这种聚集是并行化的。 而在fold和reduce函数的运算过程中,每个分区中需要进行串行处理,每个分区串行计算完结果,结果再按之前的方式进行聚集,并返回最终聚集结果。
aggregate[B](z: B)(seqop: (B,A) => B,combop: (B,B) => B): B

val arr = Array(1,2,3,4)
val rdd: RDD[Int] = sc.parallelize(arr,4)
// 参数一  区内计算  参数二区之间的运算
val res: Int = rdd.aggregate(10)(_+_ , _*_)  //    11*12*13*14*10
println(res)

注意: 分区内的数据聚合在worker下的executor中执行 ;分区间的聚合在driver端进行

1.4 collect

collect的作用
Spark内有collect方法,是Action操作里边的一个算子,这个方法可以将RDD类型的数据转化为数组,你可以随时val arr = data.collect(),将RDD类型数据转化为数组来存放并参与后续运算。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
  val rdd: RDD[Int] = sc.parallelize(arr, 3)
  // 返回本地一个数组
  val res: Array[Int] = rdd.collect()
  sc.stop()
}

​ 首先,从时间上来讲,前边已经说过了,collect是Action里边的,根据RDD的惰性机制,真正的计算发生在RDD的Action操作。由于collect是从各节点将数据拉到driver端,需要重新分区,所以,一次collect就会导致一次Shuffle,而一次Shuffle调度一次stage,然而一次stage包含很多个已分解的任务碎片Task。这么一来,会导致程序运行时间大大增加,属于比较耗时的操作,即使是在local模式下也同样耗时。
​ 其次,从环境上来讲,本机local模式下运行并无太大区别,可若放在分布式环境下运行,一次collect操作会将分布式各个节点上的数据汇聚到一个driver节点上,而这么一来,后续所执行的运算和操作就会脱离这个分布式环境而相当于单机环境下运行,这也与Spark的分布式理念不合。
​ 最后,将大量数据汇集到一个driver节点上,并且像这样val arr = data.collect(),将数据用数组存放,占用了jvm堆内存,可想而知,是有多么轻松就会内存溢出。
如何规避
​ 若需要遍历RDD中元素,大可不必使用collect,可以使用foreach语句;
​ 若需要打印RDD中元素,可用take语句,data.take(1000).foreach(println),这点官方文档里有说明;
​ 若需要查看其中内容,可用saveAsTextFile方法。

总之,单机环境下使用collect问题并不大,但分布式环境下尽量规避,如有其他需要,手动编写代码实现相应功能就好。
补充:
collectPartitions:同样属于Action的一种操作,同样也会将数据汇集到Driver节点上,与collect区别并不是很大,唯一的区别是:collectPartitions产生数据类型不同于collect,collect是将所有RDD汇集到一个数组里,而collectPartitions是将各个分区内所有元素存储到一个数组里,再将这些数组汇集到driver端产生一个数组;collect产生一维数组,而collectPartitions产生二维数组。
例:
RDD类型data,数据类型为[labeledPoint],labeledPoint为(label,features)
那么 val collectArr = data.collect();(collectArr内数组元素为labeledPoint[label,features])
而val collectPArr= data.collectPartitions();(collectPArr内数组元素为Array[label,features],即为二维数组)

1.5 foreach

对数据集中每一个元素运行函数function。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
  val rdd: RDD[Int] = sc.parallelize(arr, 3)
  // 直接对RDD中的每个元素再本客户端操作 一般用于打印展示在控制台
  rdd.foreach(e => println(e * 10))
  sc.stop()
}

1.6 collectAsMap

collectAsMap对(K,V)型的RDD数据返回一个单机HashMap。 对于重复K的RDD元素,后面的元素覆盖前面的元素。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  val arr1 = Array("a", "b", "c", "c")
  val arr2 = Array(1, 2, 3, 4)
  //拉链成对偶
  val rdd: RDD[(String, Int)] = sc.parallelize(arr1.zip(arr2),3)
  // 返回本地的map集合
  val mp: collection.Map[String, Int] = rdd.collectAsMap()
  mp.foreach(println(_))
  sc.stop()
}

1.7 count/sum/min/max

count 返回整个 RDD 的元素个数。
  内部函数实现为:
  defcount():Long=sc.runJob(this,Utils.getIteratorSize_).sum 将每个分区中的元素的个数累加起来!

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  val arr1 = Array("a", "b", "c", "c")
  val arr2 = Array(1, 2, 3, 4)
  //拉链成对偶
  val rdd: RDD[(String, Int)] = sc.parallelize(arr1.zip(arr2),3)
  val l: Long = rdd.count()
  println(l)
  sc.stop()
}

将每个分区中的数据进行计数 然后将结果返回

1.8 countByKey/countByValue

用于统计RDD[K,V]中每个K的数量,返回具有每个key的计数的(k,int)pairs的hashMap

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  val arr1 = Array("a", "b", "c", "c")
  val arr2 = Array(1, 3, 2, 3)
  //拉链成对偶
  val rdd: RDD[(String, Int)] = sc.parallelize(arr1.zip(arr2),3)
  val mp: collection.Map[String, Long] = rdd.countByKey()
  mp.foreach(println(_))
  sc.stop()
}
(c,2)
(a,1)
(b,1)

1.9 first

返回数据集的第一个元素(类似于take(1)),返回0号分区的第一个元素!

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  val arr1 = Array("a", "b", "c", "c")
  val arr2 = Array(1, 3, 2, 3)
  //拉链成对偶
  val rdd: RDD[(String, Int)] = sc.parallelize(arr1.zip(arr2),3)
  println(rdd.first())
  sc.stop()
}

1.10 take

返回一个包含数据集前n个元素的数组(从0下标到n-1下标的元素),不排序。

有可能会触发多个action ,获取数据的时候 如果一个分区中的数据的个数满足返回, 不满足再从下一个分区中获取数据

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  // 读取本地文件中的数据
  val rdd: RDD[String] = sc.textFile("d://word.txt")
  // 切割每行数据
  val rdd2: RDD[String] = rdd.flatMap(_.split("\\s"))
  //返回一个包含数据集前n个元素的数组(从0下标到n-1下标的元素),不排序。
val res: Array[String] = rdd2.take(4)
  res.foreach(println)
  sc.stop()
}

1.11 takeSample

对于一个数据集进行随机抽样,返回一个包含num个随机抽样元素的数组,withReplacement表示是否有放回抽样,参数seed指定生成随机数的种子。
该方法仅在预期结果数组很小的情况下使用,因为所有数据都被加载到driver端的内存中。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  // 读取本地文件中的数据
  val rdd: RDD[String] = sc.textFile("d://word.txt")
  // 切割每行数据
  val rdd2: RDD[String] = rdd.flatMap(_.split("\\s"))
  // 获取样本数据  返回到本地 Driver端3个数据
  val res: Array[String] = rdd2.takeSample(false, 3, 1)
  res.foreach(println)
  sc.stop()
}

1.12 takeOrdered

返回RDD中前n个元素,并按默认顺序排序(升序)或者按自定义比较器顺序排序。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  // 读取本地文件中的数据
  val rdd: RDD[String] = sc.textFile("d://word.txt")
  // 切割每行数据
  val rdd2: RDD[String] = rdd.flatMap(_.split("\\s"))
  //获取前n个元素 返回排好序的数据
  val res: Array[String] = rdd2.takeOrdered(4)
  res.foreach(println)
  sc.stop()
}

课堂代码

  val sc: SparkContext = SparkUtils.getSparkContext()
    val rdd1: RDD[Int] = sc.makeRDD(List(4, 8, 2, 9), 2)
    // 定义方法  对Int类型排序
   implicit def myOrdered:Ordering[Int]={
    
    
      new  Ordering[Int] {
    
    
        override def compare(x: Int, y: Int): Int = y-x
      }
    }

   // 对数据进行排序 ,排序规则是一个隐式参数默认对Int类型 升序
    // 是一个柯里化的方法  参数列表二 排序规则  隐式变量(函数)
    // 指定排序规则, 写一个排序方法
    val res: Array[Int] = rdd1.takeOrdered(2)
    println(res.toList)
    sc.stop()
---------------------------------------------------
  val sc: SparkContext = SparkUtils.getSparkContext()
    val rdd1: RDD[User] = sc.makeRDD(List(new User("zss", 21), new User("lss", 33), new User("ww", 36)), 2)
    // 定义方法  对Int类型排序
  /*  def myOrdered:Ordering[Int]={
      new  Ordering[Int] {
        override def compare(x: Int, y: Int): Int = y-x
      }
    }*/
  // 隐式方法
   implicit def ord:Ordering[User]={
    
    
      new Ordering[User] {
    
    
        override def compare(x: User, y: User): Int = y.age-x.age
      }
    }

   // 对数据进行排序 ,排序规则是一个隐式参数默认对Int类型 升序
    // 是一个柯里化的方法  参数列表二 排序规则
    // 指定排序规则, 写一个排序方法
    val res: Array[User] = rdd1.takeOrdered(2)
    println(res.toList)
    sc.stop()
---------------------------------------------------------
  val sc: SparkContext = SparkUtils.getSparkContext()
    val rdd1: RDD[User2] = sc.makeRDD(List(User2("zss", 21),  User2("lss", 33),  User2("ww", 36)), 2)


    // 定义方法  对Int类型排序
  /*  def myOrdered:Ordering[Int]={
      new  Ordering[Int] {
        override def compare(x: Int, y: Int): Int = y-x
      }
    }*/
  // 隐式方法
   implicit def ord:Ordering[User2]={
    
    
      new Ordering[User2] {
    
    
        override def compare(x: User2, y: User2): Int = y.age-x.age
      }
    }
   // 对数据进行排序 ,排序规则是一个隐式参数默认对Int类型 升序
    // 是一个柯里化的方法  参数列表二 排序规则
    // 指定排序规则, 写一个排序方法
    val res: Array[User2] = rdd1.takeOrdered(2)
    println(res.toList)
    sc.stop()

/**
 * Author:   Hang.Z
 * Date:     21/06/08 
 * Description:
 * 以后有封装数据的需求的时候使用样例类
 *   实现了序列化
 *   重写的toString
 *   hashCode  equals
 */
case class User2(name:String,age:Int)

1.13 top

top可返回最大的k个元素。 函数定义如下。
top(num:Int)(implicit ord:Ordering[T]):Array[T]
相近函数说明如下。
·top返回最大的k个元素,指定排序规则。
·take返回最小的k个元素。
·takeOrdered返回最小的k个元素,并且在返回的数组中保持元素的顺序,指定排序规则。
·first相当于top(1)返回整个RDD中的前k个元素,可以定义排序的方式Ordering[T]。
返回的是一个含前k个元素的数组。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  // 读取本地文件中的数据
  val rdd: RDD[String] = sc.textFile("d://word.txt")
  // 切割每行数据
  val rdd2: RDD[String] = rdd.flatMap(_.split("\\s"))
  //获取前n个元素 his排好序的数据
  val res: Array[String] = rdd2.top(4)
  res.foreach(println)
  sc.stop()
}

1.14 saveAsTextFile

函数将数据输出,存储到 HDFS 的指定目录或者是本地目录中。

def main(args: Array[String]): Unit = {
    
    
  val sc: SparkContext = SparkUtil.getSparkContext()
  // 读取本地文件中的数据
  val rdd: RDD[String] = sc.textFile("d://word.txt")
  // 切割每行数据
  val rdd2: RDD[String] = rdd.flatMap(_.split("\\s+"))
  //将结果数据写到指定的文件夹下  根据分区的个数存储到不同的文件中
  rdd2.repartition(3).map((_, 1)).reduceByKey(_ + _).sortBy(_._2).saveAsTextFile("d://res2")
  sc.stop()
}
将结果写入到HDFS中
def main(args: Array[String]): Unit = {
    
    
  System.setProperty("HADOOP_USER_NAME" , "root")
  val sc: SparkContext = SparkUtil.getSparkContext()
  // 读取本地文件中的数据
  val rdd: RDD[String] = sc.textFile("d://word.txt")
  // 切割每行数据
  val rdd2: RDD[String] = rdd.flatMap(_.split("\\s+"))
  //将结果数据写到指定的文件夹下  根据分区的个数存储到不同的文件中
  rdd2.repartition(3).map((_, 1)).reduceByKey(_ + _).sortBy(_._2).saveAsTextFile("hdfs://doit01:9000/res")
  sc.stop()
}

1.15 saveAsObjectFile

saveAsObjectFile将分区中的每10个元素组成一个Array,然后将这个Array序列化,映射为(Null,BytesWritable(Y))的元素,写入HDFS为SequenceFile的格式。

下面代码为函数内部实现。

map(x=>(NullWritable.get(),new BytesWritable(Utils.serialize(x))))

图中的左侧方框代表RDD分区,右侧方框代表HDFS的Block。 通过函数将RDD的每个分区存储为HDFS上的一个Block。

**

本系列文档完整视频教程

**

猜你喜欢

转载自blog.csdn.net/qq_37933018/article/details/118069144