什么是RDD
RDD叫做弹性分布式数据集,是Spark中基本的数据抽象,在代码中是一个抽象类,它是一个弹性的、不可变的、可分区的,并且可并行计算的集合.
RDD的特性
- 一组分区;对于RDD来说,每个分区被一个计算任务处理,并决定了并行计算的粒度;
- 一个计算每个分区的函数;Spark中的计算是以分片为单位,每个RDD都是实现compute函数来实现计算;
- RDD之间的依赖关系;RDD每次转换都会生成一个新的RDD,即RDD之间就会形成前后依赖关系,在部分分区丢失时,Spark可以通过依赖关系重新计算丢失的分区数据,而不是计算所有的分区的数据;
- 一个分片函数;对与键值对的RDD,需要一个分区器,来控制数据的流向,并且决定了分区的个数;
- 一个列表,存储每个分区的优先未知,这个是根据了移动数据不如移动计算的理念,尽可以的把计算和数据在同一机器中,这样可以节约网络流量。
RDD创建
- 从集合中创建
val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8,9,10))
- 从外部存储系统的数据创建
val lineWordRdd: RDD[String] = sc.textFile("inputtxt")
- 从其他RDD创建
val value: RDD[(String, Int)] = sc.textFile("data/test.txt").flatMap(line => line.split(" ")).map(word => (word, 1)).reduceByKey(_ + _)
RDD分区
RDD自定义分区
object WordCount {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("WC")
val sc: SparkContext = new SparkContext(conf)
val value: RDD[(String, Int)] = sc.textFile("data/1.txt")
.flatMap(line => line.split(" ")).map(word => (word, 1)).reduceByKey(_ + _)
.partitionBy(new CustomPartitioner(2))
value.foreach(println)
sc.stop()
}
class CustomPartitioner(num:Int) extends Partitioner{
override def numPartitions: Int = num
override def getPartition(key: Any): Int = {
if(key == 1){
0
} else{
1
}
}
}
}
我们自定义的分区函数,继承Partitioner,传递分区的个数,并且自己实现数据分区的策略。自己调用partitionBy(new CustomPartitioner(2)),让RDD调用我们自己的分区函数。
Spark分区器源码
abstract class Partitioner extends Serializable {
def numPartitions: Int
def getPartition(key: Any): Int
}
def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
val rdds = (Seq(rdd) ++ others)
val hasPartitioner = rdds.filter(_.partitioner.exists(_.numPartitions > 0))
val hasMaxPartitioner: Option[RDD[_]] = if (hasPartitioner.nonEmpty) {
Some(hasPartitioner.maxBy(_.partitions.length))
} else {
None
}
val defaultNumPartitions = if (rdd.context.conf.contains("spark.default.parallelism")) {
rdd.context.defaultParallelism
} else {
rdds.map(_.partitions.length).max
}
// If the existing max partitioner is an eligible one, or its partitions number is larger
// than or equal to the default number of partitions, use the existing partitioner.
if (hasMaxPartitioner.nonEmpty && (isEligiblePartitioner(hasMaxPartitioner.get, rdds) ||
defaultNumPartitions <= hasMaxPartitioner.get.getNumPartitions)) {
hasMaxPartitioner.get.partitioner.get
} else {
new HashPartitioner(defaultNumPartitions)
}
}
如果RDD的最大分区数据大于等于默认的分区数,取RDD的最大分区数;否则取默认分区数。
- HashPartitioner:默认分区函数,按key求取hash值,再对hash值除以分区个数取余,如果余数<0,则用余数+分区的个数,最后返回的值就是这个key所属的分区ID。
- RangePartitioner:基于范围的分区,用于可排序记录的分区。它通过对输入的RDD进行采样来确定分区的边界,然后根据键的大小将元素分配到相应的分区。默认情况下,它按升序分区,也可以设置降序分区。它还可以设置每个分区的采样点的数量。
RangePartitioner实现过程:
1.先从整个RDD中采用水塘抽样算法,抽取样本数据,将样本数据排序,计算出峰哥分区最大的key值,形成一个Array[Key]类型的数组变量rangeBounds;
2.判断key在rangeBounds中所处的范围,给出该key在下一个RDD中分区id下标,该分区器要求RDD中key类型是必须可以排序的。