RDD的操作

RDD的创建

三种创建方式

  • 从内存中创建
  1. 使用parallelize
	val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8))
  1. 使用makeRDD
	val rdd1 = sc.makeRDD(Array(1,2,3,4,5,6,7,8))
  • 从外部存储创建(往后看)
  • 由其他RDD转换(往后看)

传递给RDD的函数

运行在集群中的Spark API 强依赖于 driver 程序中给RDD传入的函数。官方推荐以下两种方式:

  • 简短的匿名函数
  • 伴生对象的静态方法
	object MyFunctions {
	  def func1(s: String): String = { ... }
	}
	
	myRdd.map(MyFunctions.func1)

除了使用静态方法以外,编码中也会出现传递一个实例方法的引用,但是这样会导致整个实例对象会被序列化发送到集群:

	//等同于rdd.map(x => this.func1(x))
	class MyClass {
	  def func1(s: String): String = { ... }
	  def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) }
	}

如果抛序列化的异常,使类继承scala.Serializable即可。
访问类实例属性也有类似情况。

	//等同于rdd.map(x => this.field + x)
	class MyClass {
	  val field = "Hello"
	  def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(x => field + x) }
	}
	//为了避免这种情况可以这么做
	def doStuff(rdd: RDD[String]): RDD[String] = {
	  val field_ = this.field
	  rdd.map(x => field_ + x)
	}

理解闭包

理解Spark集群中执行的变量、方法的作用域和生命周期是个难点。
RDD 操作其作用域范围以外的变量常常会带来迷惑。
以下例子,就算运行在local模式同一JVM中,结果也跟想象不同:

	var counter = 0
	var rdd = sc.parallelize(data)
	// Wrong: Don't do this!!
	rdd.foreach(x => counter += x)
	println("Counter value: " + counter)

Job运行过程中,Spark将RDD操作分割成一个个Task,序列化后分发到Executor上执行。Executor执行的内容称作闭包。这个闭包中的变量、方法必须对Executor可见,比如上例foreach()中涉及的部分。我们知道java对象经过序列化和反序列化以后,旧对象跟新对象是不一样的,所以Executor上的新闭包是一个副本,修改也是作用在副本上。所以上例打印的是0。

类似的情况,比如使用 rdd.foreach(println) or rdd.map(println)打印RDD的数据。在local模式单机上跑,Exexutor、Driver都在一个JVM中,可以在控制台上看到打印,但是在集群模式下,它是在Exexutor上打印,而不是在Driver端打印。要想在Driver上打印,需要使用collect()把整个RDD的数据抓取过来再打印,如果数据量大可能OOM,安全的做法是使用take(),比如rdd.take(100).foreach(println)

RDD转换算子

从操作对象上,分为value 类型和 key-value类型

value类型

map(func)
意义:将每一个输入元素经过func函数转换映射成新元素

	scala> var source = sc.parallelize(1 to 10)

	scala> val mapadd = source.map(_ * 2)
	
	scala> mapadd.collect()
	res8: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

filter(func)
意义:过滤func函数计算后返回值为true的元素

	scala> var sourceFilter = sc.parallelize(Array("laozhang","laoli","woqu","daye"))
	
	scala> val filter = sourceFilter.filter(_.contains("lao"))
	
	scala> filter.collect()
	res5: Array[String] = Array(laozhang, laoli)

flatMap(func)
意义:将每一个输入元素经过func函数转换映射成新的0或多个元素(func返回一个Seq )

	scala> val sourceFlat = sc.parallelize(1 to 5)

	scala> val flatMap = sourceFlat.flatMap(1 to _)
	
	scala> flatMap.collect()
	res22: Array[Int] = Array(1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5)

mapPartitions(func)
意义:以每一个分区为单位经func函数处理(func类型Iterator[T] => Iterator[U])
跟map的区别:
map每次处理一条数据。mapPartition每次处理一个分区,每个分区处理完以前,数据不能回收,可能导致OOM,但是效率比map高。

	scala> val rdd = sc.parallelize(Array(1,2,3,4))

	scala> val mapRdd = rdd.mapPartitions(x=>x.map(_*2))
	
	scala> mapRdd .collect()
	res15: Array[Int] = Array(2, 4, 6, 8)

mapPartitionsWithIndex(func)
意义:以每一个分区为单位经func函数处理,多了一个分区号(func类型(Int, Interator[T]) => Iterator[U])

	scala> val rdd = sc.parallelize(Array(1,2,3),2)

	scala> val indexRdd = rdd.mapPartitionsWithIndex((index,itr)=>(itr.map((index,_))))
	
	scala> indexRdd.collect()
	res15: Array[(Int, Int)] = Array((0,1), (1,2), (1,3))

glom(func)
意义:把每一个分区的数据整个作为一个数组的元素

	scala> val rdd = sc.parallelize(1 to 16,4)
	
	scala> rdd.glom().collect()
	res17: Array[Array[Int]] = Array(Array(1, 2, 3, 4), Array(5, 6, 7, 8), Array(9, 10, 11, 12), Array(13, 14, 15, 16))

groupBy(func)
意义:按func函数的返回值进行分组

	scala> val rdd = sc.parallelize(1 to 4)
	
	scala> val group = rdd.groupBy(_%2)
	
	scala> group.collect
	res9: Array[(Int, Iterable[Int])] = Array((0,CompactBuffer(2, 4)), (1,CompactBuffer(1, 3)))

sample(withReplacement,fraction,seed)
意义:随机抽样出fraction比例数量的数据,withReplacement表示抽出的数据是否放回,true有放回,false无放回,seed随机生成器种子

	scala> val rdd = sc.parallelize(1 to 10)
	
	scala> var sample1 = rdd.sample(true,0.4,2)
	
	scala> sample1.collect()
	res10: Array[Int] = Array(1, 5, 5, 6, 7, 7, 8, 9)
	
	scala> var sample2 = rdd.sample(false,0.8,12)
	
	scala> sample2.collect()
	res11: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)

distinct([numTasks])
意义:去重。可选参数numTasks指并行度

	scala> val dataRdd = sc.parallelize(List(1,1,1,5,9,9,2,1))
	
	scala> val disRDD = dataRdd.distinct()
	
	scala> disRDD.collect
	res0: Array[Int] = Array(1, 2, 9, 5)

coalesce(numPartitions)
意义:减少分区数。可以指定是否shuffle

	scala> val rdd = sc.parallelize(1 to 16,4)
	
	scala> rdd.getNumPartitions
	res1: Int = 4
	
	scala> val coalesceRDD = rdd.coalesce(2)
	
	scala> coalesceRDD.getNumPartitions
	res2: Int = 2

repartition(numPartitions)
意义:重分区。是coalesce的封装,一定执行shuffle

	def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
	  coalesce(numPartitions, shuffle = true)
	}
	scala> val rdd = sc.parallelize(1 to 16,4)
	
	scala> rdd.getNumPartitions
	res3: Int = 4
	
	scala> val reRdd = rdd.repartition(2)
	
	scala> reRdd.getNumPartitions
	res4: Int = 2

sortBy(func,[ascending], [numTasks])
意义:以func函数返回值大小来排序,默认正序

	scala> val rdd = sc.parallelize(List(2,3,1,4))
	//按自身大小排序
	scala> rdd.sortBy(x => x).collect()
	res5: Array[Int] = Array(1, 2, 3, 4)
	//按余数大小排序
	scala> rdd.sortBy(x => x%2).collect()
	res6: Array[Int] = Array(2, 4, 1, 3)

pipe(command, [envVars])
意义:对每个分区执行脚本(Perl 或 bash)

举例脚本:

#!/bin/sh
while read line; do
   echo "---->"${line}
done
	scala> val rdd = sc.parallelize(List("hello","world","lao","zhang"),1)
	
	scala> rdd.pipe("/home/hadoop/spark/pipe.sh").collect()
	res19: Array[String] = Array(---->hello, ---->world, ---->lao, ---->zhang)

union(otherDataset)
意义:两个RDD做并集

	scala> val rdd1 = sc.parallelize(1 to 5)
	
	scala> val rdd2 = sc.parallelize(5 to 8)
	
	scala> val rdd3 = rdd1.union(rdd2)
	
	scala> rdd3.collect
	res7: Array[Int] = Array(1, 2, 3, 4, 5, 5, 6, 7, 8)

subtract (otherDataset)
意义:两个RDD做差集

	scala> val rdd = sc.parallelize(1 to 6)
	
	scala> val rdd1 = sc.parallelize(4 to 10)
	
	scala> rdd1.subtract(rdd).collect
	res0: Array[Int] = Array(7, 8, 9, 10)

intersection (otherDataset)
意义:两个RDD做交集

	scala> val rdd1 = sc.parallelize(1 to 7)
	
	scala> val rdd2 = sc.parallelize(5 to 10)
	
	scala> rdd1.intersection(rdd2).collect
	res1: Array[Int] = Array(6, 7, 5)

cartesian(otherDataset)
意义:两个RDD做笛卡尔积。是个重操作

	scala> val rdd1 = sc.parallelize(1 to 3)
	
	scala> val rdd2 = sc.parallelize(2 to 5)
	
	scala>  rdd1.cartesian(rdd2).collect()
	res2: Array[(Int, Int)] = Array((1,2), (1,3), (1,4), (1,5), (2,2), (2,3), (2,4), (2,5), (3,2), (3,3), (3,4), (3,5))

zip(otherDataset)
意义:将两个RDD拉链。要求两个RDD分区数、元素个数都必须一致,否则抛异常

	scala> val rdd1 = sc.parallelize(Array(1,2,3),3)
	
	scala> val rdd2 = sc.parallelize(Array("one","two","three"),3)
	
	scala> rdd1.zip(rdd2).collect
	res3: Array[(Int, String)] = Array((1,one), (2,two), (3,three))

key-value类型

groupByKey([numPartitions])
意义:按每个key分组,返回包含value的Iterable
若仅仅是做一个分组求和或平均的聚合操作,reduceByKey 或 aggregateByKey 比 groupByKey 有更好的性能。比如reduceByKey 在shuffle之前会按key进行merge,类似MapReduce的combine,数据量大减

	scala> val words = Array("one", "two", "two", "three", "three", "three")
	
	scala> val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))
	
	scala> val group = wordPairsRDD.groupByKey()
	
	scala> group.collect()
	res5: Array[(String, Iterable[Int])] = Array((three,CompactBuffer(1, 1, 1)), (two,CompactBuffer(1, 1)), (one,CompactBuffer(1)))
	
	scala> group.map(t => (t._1, t._2.sum)).collect
	res6: Array[(String, Int)] = Array((three,3), (two,2), (one,1))

reduceByKey(func, [numPartitions])
意义:按每个key分组来聚合操作,func类型必须是(V,V) => V

	scala> val rdd = sc.parallelize(List(("a",1),("b",2),("b",3),("a",4)))
	
	scala> val reduce = rdd.reduceByKey((x,y) => x+y).collect
	reduce: Array[(String, Int)] = Array((a,5), (b,5))

aggregateByKey(zeroValue)(seqOp, combOp, [numPartitions])
zeroValue:对每一个分区中的每一个key操作的初始值
seqOp:在每一个分区中用初始值按key迭代value进行函数运算
combOp:合并每个分区中的结果

意义:先在每个分区中按key进行分组,每一个key组使用zeroValue与每一个value执行combOp函数运算,得到一个新的key-value对。此时,每个分区生成1或多个k-v对。然后在合并阶段,将每个分区生成的k-v对按照key再分组,每个key分组使用combOp函数运算每一个value,得到合并后新的k-v对。

	scala> val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
	
	scala> val agg = rdd.aggregateByKey(0)(math.max(_,_),_+_)
	
	scala>  agg.collect()
	res7: Array[(String, Int)] = Array((b,3), (a,3), (c,12))

foldByKey(zeroValue)(func, [numPartitions])
意义:是aggregateByKey简化,seqop和combop变成相同的func

	scala> val rdd = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)
	
	scala>  val agg = rdd.foldByKey(0)(_+_)
	
	scala> rdd.foldByKey(0)(_+_).collect
	res8: Array[(Int, Int)] = Array((3,14), (1,9), (2,3))

combineByKey(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
createCombiner:使用每一种key的第一个kv对和初始值创建Combinner形成新的ck-cv对。其中ck是第一个kv的v,cv是初始值
mergeValue:在分区中,使用Combinner形成新的ck-cv与剩下kv对的v进行运算,形成新的(k,(ck-cv))
mergeCombiners:分区间进行按k进行shuffle执行mergeCombiners合并,进而形成新的(k,(ck-cv))

意义:是aggregateByKey简化,seqop和combop变成相同的func

	scala> val rdd = sc.parallelize(Array(("a", 80), ("b", 90), ("a", 95), ("b", 95), ("a", 90), ("b", 100)),2)
	
	scala> val combine = rdd.combineByKey((_,1),(acc:(Int,Int),v)=>(acc._1+v,acc._2+1),(acc1:(Int,Int),acc2:(Int,Int))=>(acc1._1+acc2._1,acc1._2+acc2._2))
	
	scala> combine.collect
	res9: Array[(String, (Int, Int))] = Array((b,(285,3)), (a,(265,3)))

sortByKey([ascending], [numPartitions])
意义:按key进行排序(k,v)。要求k必须实现Ordered接口

	scala> val rdd = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))
	
	scala> rdd.sortByKey(true).collect()
	res10: Array[(Int, String)] = Array((1,dd), (2,bb), (3,aa), (6,cc))

mapValues(func)
意义:仅对k-v中的v操作func运算

	scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
	
	scala> rdd.mapValues("<"+_+">").collect()
	res11: Array[(Int, String)] = Array((1,<a>), (2,<b>), (3,<c>))

__ join(otherDataset, [numTasks])__
意义:按key连接两个RDD的数据。连接后的v是个tuple

	scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
	
	scala> val rdd1 = sc.parallelize(Array((1,"e"),(2,"f"),(3,"g")))
	
	scala>  rdd.join(rdd1).collect()
	res12: Array[(Int, (String, String))] = Array((1,(a,e)), (2,(b,f)), (3,(c,g)))

cogroup(otherDataset, [numTasks])
意义:按key分别聚合两个RDD的数据。联合分组后(key,(Iterable,Iterable))

scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))

scala> val rdd1 = sc.parallelize(Array((1,4),(2,5),(3,6)))

scala>  rdd.cogroup(rdd1).collect()
res13: Array[(Int, (Iterable[String], Iterable[Int]))] = Array((1,(CompactBuffer(a),CompactBuffer(4))), (2,(CompactBuffer(b),CompactBuffer(5))), (3,(CompactBuffer(c),CompactBuffer(6))))

RDD行动算子

reduce(func)
意义:先聚合分区内数据,再聚合分区间数据

scala> val rdd = sc.makeRDD(Array(("a",1),("b",3),("c",3),("d",5)))

scala> rdd.reduce((x,y)=>(x._1 + y._1,x._2 + y._2))
res15: (String, Int) = (bcad,12)

collect()
意义:一般使用在Driver中收集所有数据

count()
意义:统计元素个数

first()
意义:取第一个元素

take(n)
意义:取前n个元素

takeOrdered(n)
意义:取排序后的前n个元素

aggregate(zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
意义:分区内先将每个元素使用seqOp和初始值进行聚合,然后再使用combOp进行分区间进行运算。返回值不要求与RDD数据类型一致

	scala> var rdd1 = sc.makeRDD(1 to 10,2)
	
	scala> rdd1.aggregate(0)(_+_,_+_)
	res17: Int = 55

fold(zeroValue)(func)
意义:aggregate的简化,seqOp和combOp一样同为func。

	scala> var rdd = sc.makeRDD(1 to 10,2)
	
	scala> rdd.fold(0)(_+_)
	res17: Int = 55

saveAsTextFile(path)
意义:以文本形式保存到HDFS或者其他支持的文件系统

saveAsTextFile(path)
意义:以文本形式保存到HDFS或者其他支持的文件系统

__saveAsSequenceFile(path) __
意义:以Hadoop sequencefile格式的形式保存到HDFS或者其他支持的文件系统

saveAsObjectFile((path)
意义:以序列化的形式保存元素到HDFS或者其他支持的文件系统

countByKey()
意义:按照key统计元素的个数

foreach(func)
意义:对每一个元素迭代执行func

RDD其他常用操作

getNumPartitions
意义:获取当前RDD分区数

toDebugString
意义:查看当前RDD的血统(即依赖关系)

dependencies
意义:查看当前RDD的依赖类型(宽窄依赖)

你可能感兴趣的:(Spark)