Partition的分区规则
只有Key-Value类型的RDD才有Hash分区的,非Key-Value类型的RDD分区的是范围分区
以下代码以foreachPartition为例
1、 范围分区(RangePartitioner)
RangePartitioner基于抽样的思想来对数据进行分区
RangePartitioner作用:将一定范围内的数映射到某一个分区内,尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大,但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一个分区内。实现过程为:
第一步:先重整个RDD中抽取出样本数据,将样本数据排序,计算出每个分区的最大key值,形成一个Array[KEY]类型的数组变量rangeBounds;
第二步:判断key在rangeBounds中所处的范围,给出该key值在下一个RDD中的分区id下标;该分区器要求RDD中的KEY类型必须是可以排序的
object Spark04 {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称,使用本地模式开启两核
val conf = new SparkConf().setMaster("local[2]").setAppName("myjob")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc = new SparkContext(conf)
val rdd = sc.parallelize(List(1, 2, 3, 4, 5, 2, 3), 2)
//不是key-value所以是范围分区
rdd.foreachPartition(f => println(f.toList))
// List(1, 2, 3)
//List(4, 5, 2, 3)
}
}
2、 Hash分区(HashPartitioner)
分区规则为
def getPartition(key: Any): Int = key match {
case null => 0
case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
}
def nonNegativeMod(x: Int, mod: Int): Int = {
val rawMod = x % mod
rawMod + (if (rawMod < 0) mod else 0)
}
HashPartitioner分区的原理:对于给定的key,计算其hashCode,并除以分区的个数取余,如果余数小于0,则用余数+分区的个数(否则加0),最后返回的值就是这个key所属的分区ID。
object Spark05 {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称,使用本地模式开启两核
val conf = new SparkConf().setMaster("local[2]").setAppName("myjob")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc = new SparkContext(conf)
val rdd = sc.parallelize(List(1, 2, 3, 4, 5, 2, 3), 2)
//变成key-value
rdd.map((_, 1)).groupByKey(2).foreachPartition(f => println(f.toList))
// List((4,CompactBuffer(1)), (2,CompactBuffer(1, 1)))
//List((1,CompactBuffer(1)), (3,CompactBuffer(1, 1)), (5,CompactBuffer(1)))
}
}
3、 自定义分区
自己写一个类继承Partitioner并且重写numPartitions和getPartition方法
//自定义分区方法
class MyPartition(numPartition:Int) extends Partitioner{
//重写分区数
override def numPartitions: Int = {
numPartition
}
//分区条件
override def getPartition(key: Any): Int = {
if( key.asInstanceOf[Int] <5) 0 else 1
}
}
object Spark06 {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称,使用本地模式开启两核
val conf = new SparkConf().setMaster("local[2]").setAppName("myjob")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc = new SparkContext(conf)
val rdd = sc.parallelize(List(1, 2, 3, 4, 5, 2, 3), 2)
//自定义分区
//repartition会改变前面的分区数,变成一个区
rdd.map((_, 1)).partitionBy(new MyPartition(2)) /*.repartition(1)*/ .foreachPartition(f => println(f.toList))
//List((5,1))
//List((1,1), (2,1), (3,1), (4,1), (2,1), (3,1))
}
}
4、 默认分区数
对于makeRDD和parallelize
如果不给分区数,就会按照local[*]里的核数给定分区数,默认不超过最大核数
object Spark01 {
def main(args: Array[String]): Unit = {
//给*就是按照电脑cpu最大核数
val conf = new SparkConf().setMaster("local[*]").setAppName("myjob")
val sc = new SparkContext(conf)
//有默认分片值,是电脑cpu最大核数,我是16核所以有16个分区
val rdd=sc.parallelize(List(1,2,3,4,5))
rdd.foreachPartition(f=> println(f.toList))
}
}
对于testFile
不给分区数,系统给的分区数就是2,如果给的分区数大于2,就按照你给的分区数
object Spark01 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("myjob")
val sc = new SparkContext(conf)
//有默认分片值2
val rdd=sc.textFile("file:///C:\\Users\\wuyanxiang\\study\\hadoop\\spark01\\src\\main\\scala\\njbdqn\\words")
rdd.foreachPartition(f=> println(f.toList))
}
}