Resilient Distributed Dataset(RDD),弹性分布式数据集,是Spark上的一个核心抽象,表示用于并行计算的,不可修改的,对数据集合进行分区的分布式的数据结构。不同来源的数据 都可以经过转换变为RDD 再由Spark进行处理。
这个数据集的全部或部分可以缓存在内存中,在多次计算间重用。它是spark提供的一个特殊集合类。普通的集合数据作为一个整体,但RDD中的数据进行了分区Partition处理,这样做的目的就是为了分布式。如:传统List(1,2,3,4)是一个整体,RDD可能就是RDD(1,2) (3,4)。这样如需计算时,就把1和2发送给一个worker,把3和4发送给另一个worker。按分区完成数据的分发进行分布式运算。
主要分为三部分组成:数据分片、计算函数、RDD依赖(lineage血缘关系)
Lineage让RDD有了生命,可以进行向前的追溯,当某个节点计算错误时,只需要根据Lineage重新计算相关的操作而不必回滚整个程序,这点对懒执行和容错特别有意义。
创建RDD 有两种方式:1、直接读取外部数据源(S3, hdfs, 文件);2利用scala的集合类来构建RDD
sc.makeRDD() sc.textFile() sc.parallelize() 这个三个都可以指定分区数
rdd.collect 操作的话,会把所有partiton的数据以Array的形式返回,这样效率会很低,并且有可能数据量超过了内存 ,导致了OOM. 所以在开发中这个操作不要用了。
查看分区结构
spark并没有原生的提供rdd的分区查看工具 我们可以自己来写一个
import org.apache.spark.rdd.RDD
import scala.reflect.ClassTag
object su {
def debug[T: ClassTag](rdd: RDD[T]) = {
rdd.mapPartitionsWithIndex((i: Int, iter: Iterator[T]) => {
val m = scala.collection.mutable.Map[Int, List[T]]()
var list = List[T]()
while (iter.hasNext) {
list = list :+ iter.next
}
m(i) = list
m.iterator
}).collect().foreach((x: Tuple2[Int, List[T]]) => {
val i = x._1
println(s"partition:[$i]")
x._2.foreach { println }
})
}
}
上面的单例对象用来方便查看RDD的分区的数据
1.RDD分为两类
PairRDD
键值对类型的RDD
RDD
普通类型的RDD
2.PairRDD提供的方法
(1) aggregateByKey(zeroValue)(func1,func2)
zeroValue表示初始值,初始值会参与func1的计算
在分区内,按key分组,把每组的值进行fun1的计算
再将每个分区每组的计算结果按fun2进行计算
val rdd = sc.parallelize(List(("cat",2), ("dog",5),("cat",4),("dog",3),("cat",6),("dog",3),("cat",9),("dog",1)),2);
scala> su.debug(rdd)
partition:[0]
(cat,2)
(dog,5)
(cat,4)
(dog,3)
partition:[1]
(cat,6)
(dog,3)
(cat,9)
(dog,1)
scala> import scala.math._
scala> rdd.aggregateByKey(0)(max(_,_),_+_);
Array((dog,8), (cat,13))
(2) groupByKey
val rdd = sc.parallelize(List(("cat",2), ("dog",5),("cat",4),("dog",3),("cat",6),("dog",3),("cat",9),("dog",1)),2);
rdd.groupByKey()
(3) reduceByKey 按照键来进行合并处理
var rdd = sc.makeRDD( List( ("hello",1),("spark",1),("hello",1),("world",1) ) )
rdd.reduceByKey(_+_);
(4) join
val rdd1 = sc.makeRDD(List(("cat",1),("dog",2)))
val rdd2 = sc.makeRDD(List(("cat",3),("dog",4),("tiger",9)))
rdd1.join(rdd2);
(5) partitionBy
通常我们在创建RDD时指定分区规则 将会导致 数据自动分区
我们也可以通过partitionBy方法人为指定分区方式来进行分区
常见的分区器有:
HashPartitioner
RangePartitioner
import org.apache.spark._
var rdd = sc.makeRDD(List((2,"aaa"),(9,"bbb"),(7,"ccc"),(9,"ddd"),(3,"eee"),(2,"fff")),2);
rdd.partitionBy(new HashPartitioner(2))//按照键的 hash%分区数 得到的编号去往指定的分区 这种方式可以实现将相同键的数据 分发给同一个分区的效果
rdd.partitionBy(new RangePartitioner(2,rdd))//将数据按照键的字典顺序进行排序 再分区
3.普通RDD
(1) 集合间的操作
distinct 去重
val rdd = sc.makeRDD(List(1,3,5,7,9,3,7,10,23,7));
rdd.distinct
union 并集 -- 也可以用++实现
val rdd1 = sc.makeRDD(List(1,3,5));
val rdd2 = sc.makeRDD(List(2,4,6,8));
val rdd = rdd1.union(rdd2);
val rdd = rdd1 ++ rdd2;
intersection 交集
val rdd1 = sc.makeRDD(List(1,3,5,7));
val rdd2 = sc.makeRDD(List(5,7,9,11));
val rdd = rdd1.intersection(rdd2);
subtract 差集
val rdd1 = sc.makeRDD(List(1,3,5,7,9));
val rdd2 = sc.makeRDD(List(5,7,9,11,13));
val rdd = rdd1.subtract(rdd2);
(2) collect 收集
//将rdd分布式存储在集群中不同分区的数据 获取到一起 组成一个数组返回
//要注意 这个方法将会把所有数据搞到一个机器内 容易造成内存的溢出 在生产环境下千万慎用
rdd.collect
(3) take 获取前几个数据
val rdd = sc.makeRDD(List(52,31,22,43,14,35))
rdd.take(2)
(4) takeOrdered(n) 先将rdd中的数据进行升序排序 然后取前n个
val rdd = sc.makeRDD(List(52,31,22,43,14,35))
rdd.takeOrdered(3)
(5) top(n) 先将rdd中的数据进行降序排序 然后取前n个
val rdd = sc.makeRDD(List(52,31,22,43,14,35))
rdd.top(3)
(6) map 将函数应用到rdd的每个元素中
val rdd = sc.makeRDD(List(1,3,5,7,9))
rdd.map(_*10)
(7) filter 用来从rdd中过滤掉不符合条件的数据
val rdd = sc.makeRDD(List(1,3,5,7,9));
rdd.filter(_<5);
(8) flatMap 扁平map处理
val rdd = sc.makeRDD(List("hello world","hello count","world spark"),2)
//Array(Array(hello, world), Array(hello, count), Array(world, spark))
rdd.map(_.split{" "})
//Array[String] = Array(hello, world, hello, count, world, spark)
rdd.flatMap(_.split{" "})
(9) cache 缓存
可以为rdd设置缓存
rdd.cache()
这样当未来需要重新获取rdd中的数据时 不需要重新创建 直接可以从还从中得到数据从而提升效率
这个缓存信息可以在spark的ui管理界面中查看到
(10) persist 缓存
import org.apache.spark.storage.StorageLevel
rdd.persist(StorageLevel.MEMORY_ONLY_SER)
(11) cartesian 笛卡尔积
val rdd1 = sc.makeRDD(List(1,2,3))
val rdd2 = sc.makeRDD(List("a","b"))
rdd1.cartesian(rdd2);
(12) coalesce(n,true/false) 扩大或缩小分区
val rdd = sc.makeRDD(List(1,2,3,4,5),2)
rdd9.coalesce(3,true);//如果是扩大分区 需要传入一个true 表示要重新shuffle
rdd9.coalesce(2);//如果是缩小分区 默认就是false 不需要明确的传入
(13) repartition(n) 等价于上面的coalesce
(14) count 统计rdd中元素的个数
val rdd = sc.makeRDD(List(1,2,3,4,5),2)
rdd.count
(15) countApprox 统计rdd的元素个数 计算一个近似值
可以传入一个时间毫秒值 时间越长求出的结果精度越高
val rdd = sc.makeRDD(1 to 1000000000,5)
rdd.countApprox(5 * 1000)
(16) mapPartitionsWithIndex 分别遍历分区做不同的处理
val rdd = sc.makeRDD(List(1,2,3,4,5),2);
rdd.mapPartitionsWithIndex((i,iter)=>{
var list = List[String]()
if(i==0){
while(iter.hasNext){
list = list :+ (iter.next + "a")
}
} else {
while(iter.hasNext){
list = list :+ (iter.next + "b")
}
}
list.iterator
});
(17) saveAsTextFile 按照文本方式保存分区数据
val rdd = sc.makeRDD(List(1,2,3,4,5),2);
rdd.saveAsTextFile("/root/work/aaa")
(18) textFile 读取文件数据成为rdd
val rdd = sc.textFile("/root/work/words.txt",2);
(19) sortBy 将rdd中的数据经过函数处理根据处理结果将原始数据进行排序
val rdd = sc.makeRDD(List(123,324,1,35,23,5));
rdd.sortBy(x=>x);//升序
rdd.sortBy(x=>x,false);//降序
rdd.sortBy(x=>{if(x<100)x*1000 else x})
(20) zip 拉链操作
val rdd1 = sc.makeRDD(List("aaa","bbb","ccc"));
val rdd2 = sc.makeRDD(List(1,2,3));
rdd1.zip(rdd2)