在 Spark 中,从 HDFS 读取数据时按文件块(block)数量决定初始 partition 数 ,这一机制是通过 Hadoop InputFormat 的分片(split)策略实现的。具体流程如下:
1. HDFS 文件块(Block)与 Spark Partition 的对应关系
-
HDFS 默认块大小(如 128MB/256MB)决定了文件的物理存储分布。
-
Spark 在读取 HDFS 文件时 ,会调用 Hadoop 的
InputFormat
(如TextInputFormat
、SequenceFileInputFormat
)来生成数据分片(InputSplit
)。 -
每个
InputSplit
会映射为一个 Spark Partition ,因此:
初始 Partition 数 ≈ HDFS 文件块数(假设文件未被压缩且未手动指定分区数)。
2. 底层实现机制
(1) 调用 Hadoop API
Spark 通过 HadoopRDD
或 NewHadoopRDD
读取 HDFS 数据时,会使用 Hadoop 的 InputFormat.getSplits()
方法生成分片。
-
关键类:
-
org.apache.hadoop.mapreduce.InputFormat
-
org.apache.spark.rdd.HadoopRDD
-
(2) 分片(Split)生成逻辑
以 TextInputFormat
为例:
-
按文件块切分:
-
每个 HDFS 文件块(block)默认生成一个
FileSplit
(即一个InputSplit
)。 -
例如:一个 1GB 的文件(128MB/block)会生成 8 个
FileSplit
→ 对应 8 个 Spark Partition。
-
-
处理跨行问题:
- 如果某一行数据跨越两个 block,Hadoop 会通过 网络读取下一个 block 的开头部分,确保数据完整性。
(3) Spark 的 Partition 分配
-
生成的
InputSplit
会被封装为HadoopPartition
,最终构成 RDD 的分区:Scala// HadoopRDD 中的分区生成逻辑 override def getPartitions: Array[Partition] = { val inputSplits = inputFormat.getSplits(jobConf, minPartitions) inputSplits.map(split => new HadoopPartition(id, split.index, split)) }
- 这里的
minPartitions
是用户指定的最小分区数(默认不指定时等于inputSplits.size
)。
- 这里的
3. 特殊情况处理
(1) 压缩文件
-
如果文件是 不可分割的压缩格式 (如 GZIP),整个文件会被视为 一个
InputSplit
→ 仅生成 1 个 Partition。 -
可分割压缩格式(如 BZip2)仍按 block 切分。
(2) 手动指定分区数
-
通过
sc.textFile(path, minPartitions)
指定最小分区数时:-
Spark 会尝试合并或拆分
InputSplit
以满足minPartitions
要求。 -
但最终分区数可能 大于
minPartitions
(因为不能拆分单个 block)。
-
(3) 小文件合并
-
大量小文件会导致 Partition 数过多(每个小文件至少 1 个 Partition),可通过以下方式优化:
-
使用
coalesce
减少分区。 -
读取时指定
minPartitions
(如sc.textFile("hdfs://path/*.txt", 100)
)。
-
4. 验证方法
(1) 查看 RDD 的分区数
Scala
val rdd = sc.textFile("hdfs://path/file.txt")
println(rdd.partitions.size) // 输出实际分区数
(2) 调试 Hadoop InputSplits
Scala
val hadoopConf = sc.hadoopConfiguration
val path = new org.apache.hadoop.fs.Path("hdfs://path/file.txt")
val inputFormat = new org.apache.hadoop.mapreduce.lib.input.TextInputFormat
val splits = inputFormat.getSplits(new org.apache.hadoop.mapreduce.JobContext(hadoopConf, null))
println(splits.size) // 输出Hadoop生成的InputSplit数
5. 总结
关键点 | 说明 |
---|---|
默认规则 | 1 个 HDFS block → 1 个 InputSplit → 1 个 Spark Partition |
压缩文件 | 不可分割压缩格式(如 GZIP)→ 仅 1 个 Partition |
手动控制 | sc.textFile(path, minPartitions) 可调整分区数下限 |
优化建议 | 避免大量小文件,合理设置 minPartitions |
通过这一机制,Spark 天然适配 HDFS 的分布式存储特性,实现数据本地化(Data Locality),减少网络传输开销。