通过文件数据源创建RDD:
rdd = sc.textFile(filePath, minPartitions=None)
filePath是外部文件的路径,可以是本地文件,也可以是hdfs等。
minPartitions参数用来控制文件数据源创建RDD时所分配的最小分区数。
默认minPartitions=2
我们还可以通过源码得到这一点:
当没有设置minPartitions,即minPartitions=None
时,minPartitions=min(self.defaultParallelism, 2)
,defaultParallelism
是默认并行度。
我们知道默认的Master
是local[*]
此时对应的defaultParallelism
为:
因此,默认minPartitions=2
。
分区的设定
注意,minPartitions
是最小分区数,就是有可能最终实际分区数大于所设置的minPartitions值。
例如新建一个data.txt
将rdd作为文本文件保存到output目录下
前面说了默认分区数为2,所以应该是两个分区对吧,但其实不然,点开ouput目录
竟然是3个分区。
我们要明白这一点,Spark读取文件时,底层其实使用的是Hadoop的读取方式。
分区数量是这么计算的:
首先,你觉得1 2 3应该占3个字节,其实不然。
通过notepad++可以显示data.txt里的所有字符:
1和2后面都有一个换行符,换行符占2字节,因此整个文件的大小应该是3*2+1=7(byte)
即 totalSize = 7(byte)
默认是2个分区,则每个分区的字节数应该是 goalSize = totalSize / minPartitions = 7 / 2 = 3(byte)
,剩下还有1byte
,由于1 / goalSize = 1 / 3 > 10%
,所以应该为它再分配一个分区,因此最后总共分配了3个分区。
即只要剩余字节数 / 每分区分配字节数(goalSize) > 10%
,则应该再分配一分区。
分区数据的分配
既然是三个分区,那么我们会觉得每个分区里的数据理应依次为1 2 3
,其实不然:
我们要明确:
- Spark采用的是Hadoop的方式读取,是按行读取,和字节数没有关系。
- 数据读取时以偏移量为单位,不会重复读取。
1LF => 012 # 3字节
2LF => 345 # 3字节
3 => 6 # 1字节
- 数据分区的偏移量范围的计算:
分区0 => [0, 3] # 0 + 3 = 3
分区1 => [3, 6] # 3 + 3 = 6
分区2 => [6, 7] # 6 + 1 = 7
上面一定是闭区间
,具体原因我也不太明白,暂且当作规则^^。
则如下存储:
- 分区0存储偏移量范围为
[0, 3]
的内容,通过[0, 2]
存储1,通过3
存储2(因为是按行读取)。 - 然后分区1存储偏移量范围为
[3, 6]
的内容,因为不会重复读取, 只会通过6
存储3 - 由于偏移量范围为
[6, 7]
的内容已存储,分区2为空。
故:
分区0 => 12
分区1 => 3
分区2 =>
案例强化
最后通过一个新案例来测试上面的理论
data2.txt
1234567
89
0
显然大小为totalSize = 14(byte)
,默认还是minPartitions = 2
,则每分区字节数goalSize = 14 / 2 = 7(byte)
,刚好可以整除,因此最后是两分区。
确定偏移量:
1234567LF => 012345678
89LF => 9101112
0 => 13
确定偏移量范围:
分区1 => [0, 7]
分区2 => [7, 14]
结果:
分区1 => 1234567
分区2 => 890
理论源于实践,大家不妨自己动动手!
这里没有必要再展开讲同时读取多个文件创建RDD的分区情况,我们只要明确,各个文件的分区是独立的,依然还是单个文件的分区。