【spark2】【源码学习】【分区数】spark读取 hdfs/可分割/单个 的文件时是如何划分分区

前言

spark怎么知道读取的是本地文件还是hdfs的文件呢?

前面写过一篇博客【spark2的textFile()是怎么实例化各个不同的fs(FileSystem)】,主要是指【本地文件系统】【hadoop文件系统】,我们看到源码里最后是根据文件路径url的scheme来判断的:
file: LocalFileSystem
hdfs: DistributeFileSystem

1、获取blockSize

1、直接从InputSplit[] getSplits(JobConf job, int numSplits)说起
2、因为前部分是跟读取本地文件是一样的,所以不重复写了,具体可参考博客内 【spark读取 本地/可分割/单个 的文件时是如何划分分区】

1.1、集群blockSzie=256M,hdfs文件blockSize=256M:

在这里插入图片描述
在这里插入图片描述

1.2、集群blockSzie=256M,hdfs文件blockSize=128M:

<think> 我们正在讨论Spark分区机制。根据用户的问题,他/她想了解Spark如何进行分区以及如何判定分区大小。结合之前的对话历史和提供的引用内容,我们可以总结如下: 1. **分区的目的**:分区Spark并行计算的基本单位,合理分区可以显著提升性能[^1]。 2. **分区方式**: - **默认分区**:Spark根据数据源和配置自动划分分区。例如,从HDFS读取文件,每个Block(默认128MB)对应一个分区[^4]。 - **手动分区**:通过`repartition()`或`coalesce()`调整分区数。 - **分区器(Partitioner)**:在Shuffle操作中,Spark使用分区器决定数据如何分布到下游分区。默认是哈希分区器(HashPartitioner)[^3][^5]。 3. **分区大小判定**: - **数据量**:Spark根据数据量大小和集群配置自动调整分区数[^2]。 - **配置参数**:`spark.default.parallelism`设置默认并行度(即分区数)[^5]。 - **父RDD影响**:子RDD的分区数可能继承自父RDD或由操作类型决定(如join操作的分区数由`spark.default.parallelism`决定)[^5]。 现在,我们详细展开: ### 一、Spark分区方式 #### 1. 初始分区(数据加载) - **文件数据源**(如HDFS): - 每个文件块(Block)对应一个分区。 - 例如:HDFS块大小为128MB,则一个1GB文件会被分为8个分区(1024/128=8)[^4]。 - **集合数据源**(如`sc.parallelize()`): - 分区数由`spark.default.parallelism`决定(集群模式默认值为总核心数,本地模式为本地线程数)[^5]。 #### 2. 转换操作中的分区变化 | 操作类型 | 分区行为 | |-------------------|--------------------------------------------------------------------------| | **窄依赖操作** | 分区数不变(如`map`、`filter`) | | **宽依赖操作** | 触发Shuffle,生成新分区(如`groupByKey`、`join`) | | **重分区操作** | 显式调整分区:<br>- `repartition(numPartitions)`:增加或减少分区数<br>- `coalesce()`:仅减少分区数(无Shuffle) | #### 3. 分区器(Partitioner)的作用 - **哈希分区器(HashPartitioner)**(默认): - 分区规则:$ \text{partition} = \text{hash}(key) \\mod \\text{numPartitions} $。 - 问题:可能引起数据倾斜(某些分区数据量过大)[^3]。 - **范围分区器(RangePartitioner)**: - 按Key的范围分区(需抽样数据),用于`sortByKey`等操作。 - **自定义分区器**: - 继承`Partitioner`类,实现`numPartitions`和`getPartition`方法。 ### 二、分区大小的判定方法 #### 1. 自动判定逻辑 - **目标**:使每个分区数据量适中(通常建议128MB以内)。 - **影响因素**: - **数据源大小**:从HDFS读取分区数 ≈ 总数据量 / Block大小。 - **集群资源**:分区数不应超过总核心数的2~4倍(避免过度并行化)。 - **Shuffle操作**:新分区数由`spark.default.parallelism`控制(默认200)[^5]。 #### 2. 手动调优参数 | 参数 | 作用 | |-------------------------------|----------------------------------------------------------------------| | `spark.default.parallelism` | 设置Shuffle操作的默认分区数(如`reduceByKey`)[^5] | | `spark.sql.shuffle.partitions` | DataFrame API中Shuffle操作的分区数(默认200) | #### 3. 优化建议 - **避免倾斜**:使用`salting`技术(为Key添加随机前缀)分散热点数据[^3]。 - **合理分区数**: - 太大:任务调度开销增加。 - 太小:无法充分利用并行度。 - 公式参考:$ \text{理想分区数} = \frac{\text{总数据量}}{\text{目标分区大小}} $(目标分区大小通常为100-200MB)。 - **动态调整**: - Spark 3.0+支持自适应查询(AQE),可自动合并小分区(`spark.sql.adaptive.enabled=true`)。 ### 三、案例说明 ```scala // 示例:从HDFS加载数据并调整分区 val data = spark.read.text("hdfs://path/to/data.txt") // 原始分区数=文件块数 val words = data.flatMap(_.split(" ")) // 手动重分区(触发Shuffle) val repartitioned = words.repartition(100) // 使用分区器操作 val kvPairs = words.map(word => (word, 1)) val counts = kvPairs.reduceByKey(_ + _, 50) // 设置50个分区 ``` ### 四、总结 - **分区方式**:自动(数据源/Shuffle) + 手动(`repartition`/`coalesce`)。 - **分区大小判定**: - 初始加载:由数据源决定(如HDFS块大小)[^4]。 - Shuffle阶段:由`spark.default.parallelism`或操作参数决定[^5]。 - 优化目标:平衡数据分布,避免倾斜[^3]。 --- ### 相关问题 1. 如何检测Spark任务的数据倾斜? 2. Spark 3.0的自适应查询(AQE)如何优化分区? 3. 哈希分区器和范围分区器的适用场景分别是什么? 4. 如何自定义Spark分区器解决特定业务场景的数据分布问题? </think>### Spark分区机制详解:分区方式与分区大小判定 #### 一、Spark分区方式 Spark主要通过以下三种方式确定数据分区: 1. **基于数据源的分区**(初始分区) - 从HDFS读取:每个HDFS块(默认128MB)对应一个RDD分区[^4] - 集合数据:由`spark.default.parallelism`控制(默认=集群核心数) ```scala // 示例:集合数据分区 val rdd = sc.parallelize(1 to 1000) // 分区数=spark.default.parallelism ``` 2. **基于转换操作的分区** | 操作类型 | 分区行为 | |----------------|--------------------------------------------------------------------------| | **窄依赖操作** | 保留父RDD分区结构(如`map`, `filter`) | | **宽依赖操作** | 触发重新分区(如`groupByKey`, `join`),使用HashPartitioner(默认)[^3] | ```scala // 宽依赖示例:reduceByKey默认使用HashPartitioner val counts = rdd.reduceByKey(_ + _) // 分区数=spark.default.parallelism ``` 3. **显式重分区方法** ```scala // 增加分区(触发Shuffle) rdd.repartition(100) // 减少分区(无Shuffle) rdd.coalesce(10) ``` #### 二、分区大小判定机制 Spark通过以下策略决定分区大小和数量: 1. **分区数判定公式** $$ \text{分区数} = \max\left(\text{父RDD分区数}, \text{spark.default.parallelism}\right) $$ 特殊规则: - Join操作:取父RDD中最大分区数[^5] - 无父RDD:取`spark.default.parallelism` 2. **分区数据量控制** | 配置参数 | 默认值 | 作用说明 | |-------------------------------|----------|-----------------------------------| | `spark.sql.files.maxPartitionBytes` | 128MB | 单个分区最大数据量[^2] | | `spark.default.parallelism` | 集群核心数 | Shuffle默认分区数[^5] | | `spark.sql.shuffle.partitions` | 200 | DataFrame的Shuffle分区数 | 3. **动态分区优化(Spark 3.0+)** - 开启自适应查询(AQE): `spark.sql.adaptive.enabled=true` - 自动合并小分区(避免数据倾斜) ```sql -- 自动合并小于1MB的分区 SET spark.sql.adaptive.coalescePartitions.minPartitionSize=1MB ``` #### 三、分区策略对比 | 分区策略 | 优点 | 缺点 | 适用场景 | |------------------|---------------------------|---------------------------|-----------------------| | **HashPartitioner** (默认) | 计算简单,分布均匀 | 可能数据倾斜 | 常规聚合操作 | | **RangePartitioner** | 避免数据倾斜 | 需要数据抽样,开销较大 | 排序操作 | | **自定义分区器** | 解决业务特定分布问题 | 需开发实现 | 特殊数据分布场景 | #### 四、最佳实践建议 1. **监控分区状态** ```scala // 查看分区信息 rdd.partitions.length // 分区数 rdd.glom().collect() // 查看每个分区数据 ``` 2. **优化策略** - 分区大小控制在100-200MB(HDFS块大小对齐) - 倾斜数据处理: ```scala // 添加随机前缀解决倾斜 rdd.map(k => (s"${Random.nextInt(10)}_$k", v)) .reduceByKey(_ + _) .map(kv => (kv._1.split("_")(1), kv._2)) ``` - 读取文件合并小文件: ```scala spark.read.option("mergeSchema", "true").parquet("path") ``` #### 五、分区机制示例 ```mermaid flowchart TD A[HDFS文件] -->|128MB/块| B[初始分区=块数] B --> C[filter/map操作] C -->|窄依赖| D[保持分区数] D -->|reduceByKey| E[Shuffle Write] E -->|HashPartitioner| F[新分区数= spark.default.parallelism] F --> G[Shuffle Read] ``` > **关键结论**:Spark分区由数据源特性、操作类型和配置参数共同决定,最佳分区应平衡数据分布均匀性和任务调度开销[^1][^2][^4]。 --- ### 相关问题 1. 如何通过Spark UI监控分区数据倾斜? 2. Spark 3.0的自适应查询(AQE)如何动态调整分区? 3. DataFrame和RDD在分区策略上有哪些差异? 4. 如何处理Spark Shuffle过程中的数据倾斜问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值