公众号:数据挖掘与机器学习笔记
Spark中提供了通用接口来抽象每个RDD,包括:
分区信息:数据集的最小分片依赖关系:指向其父RDD函数:基于父RDD的计算方法划分策略和数据位置的元数据RDD的分区是一个逻辑概念,变换前后的新旧分区在物理上可能是同一块内存或存储,这种优化防止函数式不变性导致的内存需求无限扩张。在RDD操作中可以使用Partitions方法获取RDD划分的分区数,也可以设定分区数目。如果没有指定将使用默认值,而默认数值是该程序所分配到的CPU核数,如果是从HDFS文件创建,默认为文件的数据块数。
//默认两个分区 val part=sc.textFile("input/input1.txt") println(part.partitions.size) //显式设置为4个partitions val part=sc.textFile("input/input1.txt",minPartitions = 4) println(part.partitions.size)Spark在形成任务的DAG时,会尽可能把计算分配到靠近数据的位置,减少数据网络传输。当RDD产生的时候存在首选位置,如HadoopRDD分区的首选位置就是HDFS块所在的节点;当RDD分区被缓存,则计算应该发送到缓存分区所在的节点进行,再不然回溯RDD的“血统”一直找到具有首选位置属性的父RDD,并据此决定子RDD的位置。
Spark中RDD存在两种依赖:窄依赖(Narrow Dependencies)和宽依赖(Wide Dependencies)。
窄依赖:每个父RDD的分区至多被一个子RDD的分区使用宽依赖:多个子RDD的分区依赖一个父RDD的分区区别:
窄依赖允许在单个集群节点上流水线式执行,这个节点可以计算所有父级分区;宽依赖需要所有父RDD的数据可用,并且数据已经通过类MR操作Shuffle完成在窄依赖中,节点失败后的恢复更加高效。因为只有丢失的父级分区重新计算,并且这些丢失的父级分区可以并行地在不同节点上重新计算。而在宽依赖地继承关系中,单个节点地失败可能导致一个RDD的所有祖先RDD中的一些分区丢失,导致计算的重新执行。 val part = sc.textFile("input/input1.txt") val wordmap = part.flatMap(_.split(" ")).map(x => (x, 1)) println(wordmap) //wordmap的依赖关系为OneToOneDependency,属于窄依赖 wordmap.dependencies.foreach { dep => println("dependency type:" + dep.getClass) println("dependency RDD:" + dep.rdd) println("dependency partitions:" + dep.rdd.partitions) println("dependency partitions size:" + dep.rdd.partitions.length) } val wordreduce = wordmap.reduceByKey(_ + _) println(wordreduce) wordreduce.dependencies.foreach{ dep => println("dependency type:" + dep.getClass) println("dependency RDD:" + dep.rdd) println("dependency partitions:" + dep.rdd.partitions) println("dependency partitions size:" + dep.rdd.partitions.length) }RDD的基本单位是partition,计算函数都是对迭代器进行复合,不需要保存每次计算的结果。如mapPartitions对每个分区内容作为整体来处理。
val a = sc.parallelize(1 to 12, 3) a.mapPartitions { x => var res = List[(Int, Int)]() var pre = x.next() while (x.hasNext) { val cur = x.next() res ::= (pre, cur ) pre = cur } res.iterator }.foreach(t2 => print(t2))上述代码把每个分区中的元素和下一个元素组成一个Tuple,因为分区中最后一个元素没有下一个元素,所以没有(4,5)和(8,9)
分区的划分对于Shuffle类操作很关键,决定了该操作的父RDD和子RDD之间的依赖类型。在Spark中默认提供两种分区划分器:哈希分区划分器(HashPartitioner)和范围分区划分器(RangePartitioner),且Partitioner只存在于(K,V)类型的RDD中,对于非(K,V)类型的Partitioner值为None。
val mapRDD = sc.textFile("input/input1.txt") println(mapRDD.partitioner) val groupRDD = mapRDD.map(x => (x, x)).groupByKey(new HashPartitioner(4)) print(groupRDD.partitioner)[1]《图解Spark:核心技术与案里实战》