Spark RDD之Partition

本文深入解析Spark RDD中的Partition概念,包括其定义、作用以及决定Partition数量的因素。通过JdbcPartition和HadoopPartition的具体实例,说明Partition如何参与数据处理流程,并探讨了不同API对Partition数量的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概要

Spark RDD主要由Dependency、Partition、Partitioner组成,Partition是其中之一。一份待处理的原始数据会被按照相应的逻辑(例如jdbc和hdfs的split逻辑)切分成n份,每份数据对应到RDD中的一个Partition,Partition的数量决定了task的数量,影响着程序的并行度,所以理解Partition是了解spark背后运行原理的第一步。

Partition定义

 
查看spark源码,trait Partition的定义很简单,序列号index和hashCode方法。Partition和RDD是伴生的,即每一种RDD都有其对应的Partition实现,所以,分析Partition主要是分析其子类。我们关注两个常用的子类,JdbcPartition和HadoopPartition。此外,RDD源码中有5个方法,代表其组成,如下: 

第二个方法,getPartitions是数据源如何被切分的逻辑,返回值正是Partition,第一个方法compute是消费切割后的Partition的方法,所以学习Partition,要结合getPartitions和compute方法。

  • JdbcPartition例子 
    下面是Spark JdbcRDDSuite中一个例子 

    val sc = new SparkContext("local[1]", "test") 
    val rdd = new JdbcRDD( 
    sc, 
    () => { DriverManager.getConnection("jdbc:derby:target/JdbcRDDSuiteDb") }, 
    // DATA类型为INTEGER 
    "SELECT DATA FROM FOO WHERE ? <= ID AND ID <= ?", 
    1, 100, 3, 
    (r: ResultSet) => { r.getInt(1) } ).count()
     

    查看JdbcPartition实现,相比Partition,主要多了lower和upper这两个字段。 

    查看JdbcRDD的getPartitions,按照如上图所示算法将1到100分为3份(partition数量),结果为(1,33)、(34,66)、(67,100),封装为JdbcPartition并返回,这样数据切分的部分就完成了。 

    查看JdbcRDD的compute方法,逻辑清晰,将Partition强转为JdbcPartition,获取连接并预处理sql,将 
    例子中的”SELECT DATA FROM FOO WHERE ? <= ID AND ID <= ?”问号分别用Partition的lower和upper替换(即getPartitions切分好的(1,33)、(34,66)、(67,100))并执行查询。至此,JdbcPartition如何发挥作用就分析完了。

  • HadoopPartition例子 
    举个简单例子

    val sc = new SparkContext("local[1]", "test")
    sc.textFile("hdfs://your-file-path").count()
       
    • 1
    • 2


    相比Partition,HadoopPartition则多了InputSplit。 

    spark切分hdfs文件,调用的是Hadoop的API,对这块不熟的同学查看上面InputSplit的链接。 

    执行计算的逻辑也很简单,将Partition强转为HadoopPartition,HadoopPartition内有InputSplit对象。调用Hadoop API三个读取数据的相关对象,InputSplit、InputFormat和Reader,读取对应split的数据。这块需要你对Hadoop的掌握,另外我在下面会讲Hadoop split的策略。

决定partition数量的因素

Partition数量可以在初始化RDD时指定(如JdbcPartition例子),不指定的话(如HadoopPartition例子),则 
读取spark.default.parallelism配置,不同类型资源管理器取值不同,如下 

了解了默认的partition数量,再看一些具体API的partition行为

  • RDD初始化相关
Spark API partition数量
sc.parallelize(…) sc.defaultParallelism
sc.textFile(…) max(传参, block数)
val hbaseRDD = sc.newAPIHadoopRDD(…) max(传参, block数)
val jdbcRDD = new JdbcRDD(…) 传参
  • 通用transformation
filter(),map(),flatMap(),distinct() 和父RDD相同
rdd.union(otherRDD) rdd.partitions.size + otherRDD. partitions.size
rdd.intersection(otherRDD) max(rdd.partitions.size, otherRDD. partitions.size)
rdd.subtract(otherRDD) rdd.partitions.size
rdd.cartesian(otherRDD) rdd.partitions.size * otherRDD. partitions.size
  • Key-based Transformations
reduceByKey(),foldByKey(),combineByKey(), groupByKey() 和父RDD相同
sortByKey() 同上
mapValues(),flatMapValues() 同上
cogroup(), join(), ,leftOuterJoin(), rightOuterJoin() 所有父RDD按照其partition数降序排列,从partition数最大的RDD开始查找是否存在partitioner,存在则partition数由此partitioner确定,否则,所有RDD不存在partitioner,由spark.default.parallelism确定,若还没设置,最后partition数为所有RDD中partition数的最大值

上面的Partition行为我们从中挑一个细分析,就是sc.textFile(…, numPartitions)读取hdfs时的Partition数,上表给出的答案是numPartitions和block数较大者,如果不指定numPartitions,则numPartitions<=2, 分析这个问题,其实跟spark无关,要查看Hadoop源码FileInputFormat类中getSplits方法

  • 指定numPartitions 

    totalSize为待处理文件总大小,numSplits就是我们所指定的numPartitions,得到了平均的文件大小goalSize,接下来 
     
    比较计算得到的goalSize和block大小blockSize,取其中较小者,再和minSize(由属性mapreduce.input.fileinputformat.split.minsize确定,默认值为0,则minSize默认值为1)取较大的。 
    假设待处理文件大小fSize=512M(视为一个大文件,不考虑1.1系数),block大小bSize=128M,

    1. sc.textFile(…, 3) 
      根据上面的公式goalSize=512M/3 > bSize=128M 
      取其较小者bSize,则按照bSize切分,split数=512M/128=4,即partition数=4

    2. sc.textFile(…, 5) 
      根据上面的公式goalSize=512M/5 < bSize=128M 
      取其较小者goalSize,则按照goalSize切分,split数=512M/(512M/5)=5,即partition数=5

    可见指定numPartitions,小于block数时无效,大于则生效。

  • 不指定numPartitions 

    默认,传给FileInputFormat类getSplits方法的numSplits值是sc.defaultParallelism和2的较小值,所以spark.default.parallelism几乎是没用的,Partition数就是block数。那么为什么是这样的呢,感兴趣的同学看下这个讨论

Partition数量影响及调整

上面分析了决定Partition数量的因数,接下来就该考虑Partition数量的影响以及合适的值。

  • Partition数量的影响

    1. Partition数量太少 
      太少的影响显而易见,就是资源不能充分利用,例如local模式下,有16core,但是Partition数量仅为8的话,有一半的core没利用到。
    2. Partition数量太多 
      太多,资源利用没什么问题,但是导致task过多,task的序列化和传输的时间开销增大。

    那么多少的partition数是合适的呢,这里我们参考spark doc给出的建议,Typically you want 2-4 partitions for each CPU in your cluster

  • Partition调整 
    1. repartition 
      reparation是coalesce(numPartitions, shuffle = true),repartition不仅会调整Partition数,也会将Partitioner修改为hashPartitioner,产生shuffle操作。
    2. coalesce 
      coalesce函数可以控制是否shuffle,但当shuffle为false时,只能减小Partition数,无法增大。

总结

Partition对应的是不同数据源的split逻辑,首先以JdbcPartition和HadoopPartition为例,介绍了Partition的组成,以及如何发挥作用,接下来分析了常见API的Partition行为,最后简单介绍了Partition数量的影响及调整。

参考: 
https://techmagie.wordpress.com/2015/12/19/understanding-spark-partitioning/ 
https://jaceklaskowski.gitbooks.io/mastering-apache-spark/content/spark-rdd-partitions.html 
https://spark.apache.org/docs/latest/tuning.html 
https://www.mapr.com/developercentral/code/loading-hbase-tables-spark

注:图片中代码均为Spark、Hadoop源码,我稍作处理,如去掉log、metric等,使逻辑更清晰。


http://blog.youkuaiyun.com/u011564172/article/details/53611109








<think>好的,用户想了解Spark RDD的使用和概念。首先我需要回顾一下RDD的基本定义和核心特性。根据引用[3],RDD是弹性分布式数据集的缩写,是Spark对不可变分布式数据集的抽象。这意味着RDD代表一个可以并操作的元素集合,具有容错性。 接下来,我需要解释RDD的五大特性,包括分区、依赖关系、计算函数、分区器以及优先位置。这些都是RDD的核心概念,用户需要清楚这些特性如何支持容错和高效计算。 然后要讲RDD的创建方式。常见的有从外部数据源加载,比如HDFS或本地文件系统,或者通过转换已有的RDD生成新的RDD。需要举一些例子,比如parallelize方法或者textFile方法。 关于RDD的操作,分为转换(Transformations)和动作(Actions)。转换是惰性的,只有触发动作时才会执。这里可以举一些常用的转换和动作的例子,如map、filter、reduceByKey,以及collect、count等。同时要注意区分宽依赖和窄依赖,宽依赖会触发shuffle,影响性能。 容错机制方面,RDD通过血统(Lineage)来恢复丢失的分区。每个RDD记录了自己是如何从其他RDD转换而来的,这样在节点失败时可以根据这些信息重新计算丢失的数据,而不需要复制多份数据。 另外,用户可能对RDDSpark生态系统中的位置感兴趣。比如,DStream是Spark Streaming的基础,由一系列RDD组成,如引用[1]和[2]提到的。而结构化流(Structured Streaming)则是基于DataFrame,优化更多,但底层可能还是利用了RDD的特性。 需要提到RDD的适用场景,比如需要低延迟操作、精细控制数据分区,或者使用非结构化数据时。同时也要指出当需要更高级优化时,DataFrame或Dataset可能更合适,如引用[5]提到的转换器和预测器的例子,虽然那更多是ML库的内容,但能说明生态系统的多样性。 最后,生成的相关问题需要覆盖RDD与其他组件的比较,比如DStream和DataFrame,以及优化方法、容错机制和适用场景。这样用户如果有进一步的问题,可以继续深入。</think>### Spark RDD 核心概念与使用详解 #### 一、RDD 基础定义 **弹性分布式数据集(Resilient Distributed Dataset, RDD)** 是 Spark 的底层核心抽象,代表一个**不可变、分区的数据集合**,支持并操作[^3]。其核心特性可总结为五大要素: - **分区列表**:数据被划分为多个分区(Partition),每个分区在集群节点上分布式存储 - **依赖关系**:记录父 RDD 到子 RDD 的转换关系(窄依赖/宽依赖) - **计算函数**:用于从父 RDD 计算当前分区的函数 - **分区器**:定义数据分布规则(如 HashPartitioner) - **优先位置列表**:标识数据分片的最佳计算位置(数据本地性优化) #### 二、RDD 创建方式 1. **从外部数据源加载**: ```python # 从本地文件创建 rdd = sc.textFile("hdfs://path/to/file.txt") # 从集合创建 rdd = sc.parallelize([1,2,3,4,5]) ``` 2. **通过转换操作生成**: ```python mapped_rdd = rdd.map(lambda x: x*2) filtered_rdd = rdd.filter(lambda x: x>3) ``` #### 三、核心操作类型 | 操作类型 | 特点 | 示例 | |---------|------|------| | **Transformations**(转换) | 惰性执,生成新 RDD | `map()`, `filter()`, `groupByKey()` | | **Actions**(动作) | 触发实际计算 | `collect()`, `count()`, `saveAsTextFile()` | | **宽依赖操作** | 涉及数据重分布(Shuffle) | `join()`, `reduceByKey()` | #### 四、容错机制 通过**血统(Lineage)**实现容错: $$ \text{RDD}_n = f(\text{RDD}_{n-1}) = f_{n} \circ f_{n-1} \circ \cdots \circ f_1(\text{原始数据}) $$ 当某个分区丢失时,Spark 可根据这个依赖链重新计算该分区[^3]。 #### 五、应用模式示例 ```python # 词频统计经典案例 lines = sc.textFile("hdfs://...") words = lines.flatMap(lambda line: line.split(" ")) pairs = words.map(lambda word: (word, 1)) counts = pairs.reduceByKey(lambda a,b: a+b) counts.saveAsTextFile("hdfs://output") ``` #### 六、与流处理的关联 在 Spark Streaming 中,DStream 本质上是**时间维度上的 RDD 序列**: $$ \text{DStream} = \{ \text{RDD}_t \mid t \in \text{时间窗口} \} $$ 每个微批处理实际上是对 RDD 的操作[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值