(转载)spark常见算子的区别

本文详细比较了Spark中reduceByKey和groupByKey在数据聚合上的区别,强调了reduceByKey的预聚合优势和适用场景。同时,讲解了map和flatMap在操作数据时的不同之处。讨论了repartition与partitionBy在数据分区上的关键差异,尤其是在PairRDD中的应用。

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

一、reduceByKey和groupByKey的区别

1、reduceByKey:按照 key进行聚合,在 shuffle 之前有 combine(预聚合)操作,返回结果是 RDD[k,v]。

2、groupByKey:按照 key进行分组,直接进行 shuffle。开发指导:reduceByKey比 groupByKey,建议使用。但是需要注意是否会影响业务逻辑。

1、reduceByKey(func):使用 func 函数合并具有相同键的值。

val list = List("hadoop","spark","hive","spark")

val rdd=sc.parallelize(list)

val pairRdd= rdd.map((_,1))

pairRdd.reduceByKey(_+_).collect.foreach(println)

上例中,我们先是建立了一个 list,然后建立通过这个 list 集合建立一个 rdd;然后我们通过 map 函数将 list 的 rdd 转化成键值对形式的 rdd;然后我们通过 reduceByKey 方法对具有相同 key 的值进行 func(_+_)的累加操作。

(hive,1)

(spark,2)

(hadoop,1)

list: List[String]=List(hadoop, spark, hive, spark)

rdd: org.apache.spark.rdd.RDD[String]= ParallelCollectionRDD[127] at parallelize at command-3434610298353610:2pairRdd: org.apache.spark.rdd.RDD[(String, Int)]= MapPartitionsRDD[128] at map at command-3434610298353610:3

pairRdd.collect.foreach(println)//打印pairRdd

(hive,1)

(spark,1)

(hadoop,1)

(spark,1)

我们需要留意的事情是,我们调用了reduceByKey操作的返回的结果类型是

org.apache.spark.rdd.RDD[(String, Int)]

注意,我们这里的collect()方法的作用是收集分布在各个worker的数据到driver节点。

如果不使用这个方法,每个worker的数据只在自己本地显示,并不会在driver节点显示。

2、groupByKey():对具有相同key的value进行分组。

val list = List("hadoop","spark","hive","spark")

val rdd=sc.parallelize(list)

val pairRdd= rdd.map(x => (x,1))

pairRdd.groupByKey().collect.foreach(println)

得出的结果为

(hive,CompactBuffer(1))

(spark,CompactBuffer(1, 1))

(hadoop,CompactBuffer(1))

list: List[String]=List(hadoop, spark, hive, spark)

rdd: org.apache.spark.rdd.RDD[String]= ParallelCollectionRDD[130] at parallelize at command-3434610298353610:2pairRdd: org.apache.spark.rdd.RDD[(String, Int)]= MapPartitionsRDD[131] at map at command-3434610298353610:3

CompactBuffer:CompactBuffer并不是scala里定义的数据结构,而是spark里的数据结构,它继承自一个迭代器和序列,所以它的返回值是一个很容易进行循环遍历的集合。

可以看到,结果并不是把具有相同key值进行相加,而是就简单的进行了分组,生成一个sequence。因此,我们可以把groupByKey()当作reduceByKey(func)操作的一部分,reduceByKey(func)先是对rdd进行groupByKey()然后在对每个分组进行func操作。

pairRdd.reduceByKey(_+_).collect.foreach(println)

这里通过groupByKey()后调用map遍历每个分组,然后通过t => (t._1,t._2.sum)对每个分组的值进行累加。因为groupByKey()操作是把具有相同类型的key收集到一起聚合成一个集合,集合中有个sum方法,对所有元素进行求和。

注意:(k,v)形式的数据,我们可以通过 ._1,._2 来访问键和值,用占位符表示就是 _._1,_._2,这里前面的两个下划线的含义是不同的,前边下划线是占位符,后边的是访问方式。 我们记不记得 ._1,._2,._3 是元组的访问方式。我们可以把键值看成二维的元组。

3、区别:

reduceByKey()对于每个key对应的多个value进行了merge操作,最重要的是它能够先在本地进行merge操作。merge可以通过func自定义。

groupByKey()也是对每个key对应的多个value进行操作,但是只是汇总生成一个sequence,本身不能自定义函数,只能通过额外通过map(func)来实现。

使用reduceByKey()的时候,本地的数据先进行merge然后再传输到不同节点再进行merge,最终得到最终结果。

而使用groupByKey()的时候,并不进行本地的merge,全部数据传出,得到全部数据后才会进行聚合成一个sequence,

groupByKey()传输速度明显慢于reduceByKey()。

虽然groupByKey().map(func)也能实现reduceByKey(func)功能,但是,优先使用reduceByKey(func)

二、map和flatMap的区别:

三、repartition和partitionBy的区别:

repartition 和 partitionBy 都是对数据进行重新分区,默认都是使用 HashPartitioner,区别在于partitionBy 只能用于 PairRDD,但是当它们同时都用于 PairRDD时,结果却不一样:

不难发现,其实 partitionBy 的结果才是我们所预期的,我们打开 repartition 的源码进行查看:

/*** Return a new RDD that has exactly numPartitions partitions.

*

* Can increase or decrease the level of parallelism in this RDD. Internally, this uses

* a shuffle to redistribute data.

*

* If you are decreasing the number of partitions in this RDD, consider using `coalesce`,

* which can avoid performing a shuffle.

*

* TODO Fix the Shuffle+Repartition data loss issue described in SPARK-23207.*/def repartition(numPartitions: Int)(implicit ord: Ordering[T]= null): RDD[T] =withScope {undefined

coalesce(numPartitions, shuffle= true)

}/*** Return a new RDD that is reduced into `numPartitions` partitions.

*

* This results in a narrow dependency, e.g. if you go from 1000 partitions

* to 100 partitions, there will not be a shuffle, instead each of the 100

* new partitions will claim 10 of the current partitions. If a larger number

* of partitions is requested, it will stay at the current number of partitions.

*

* However, if you're doing a drastic coalesce, e.g. to numPartitions = 1,

* this may result in your computation taking place on fewer nodes than

* you like (e.g. one node in the case of numPartitions = 1). To avoid this,

* you can pass shuffle = true. This will add a shuffle step, but means the

* current upstream partitions will be executed in parallel (per whatever

* the current partitioning is).

*

* @note With shuffle = true, you can actually coalesce to a larger number

* of partitions. This is useful if you have a small number of partitions,

* say 100, potentially with a few partitions being abnormally large. Calling

* coalesce(1000, shuffle = true) will result in 1000 partitions with the

* data distributed using a hash partitioner. The optional partition coalescer

* passed in must be serializable.*/def coalesce(numPartitions: Int, shuffle: Boolean= false,

partitionCoalescer: Option[PartitionCoalescer]=Option.empty)

(implicit ord: Ordering[T]= null)

: RDD[T]=withScope {undefined

require(numPartitions> 0, s"Number of partitions ($numPartitions) must be positive.")if(shuffle) {/**Distributes elements evenly across output partitions, starting from a random partition.*/val distributePartition= (index: Int, items: Iterator[T]) =>{undefined

var position= newRandom(hashing.byteswap32(index)).nextInt(numPartitions)

items.map { t=>

//Note that the hash code of the key will just be the key itself. The HashPartitioner//will mod it with the number of total partitions.

position = position + 1(position, t)

}

} : Iterator[(Int, T)]//include a shuffle step so that our upstream tasks are still distributed

newCoalescedRDD(newShuffledRDD[Int, T, T](mapPartitionsWithIndex(distributePartition),newHashPartitioner(numPartitions)),

numPartitions,

partitionCoalescer).values

}else{new CoalescedRDD(this, numPartitions, partitionCoalescer)

}

}

即使是RairRDD也不会使用自己的key,repartition 其实使用了一个随机生成的数来当做 Key,而不是使用原来的 Key!!
————————————————
原文转自:spark算子之间的区别_spark常见算子的区别_黑岛人的博客-优快云博客icon-default.png?t=M276https://blog.youkuaiyun.com/weixin_29531989/article/details/111895318?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164716453016780274192152%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164716453016780274192152&b

<think>嗯,用户问的是Spark中的action算子和transformation算子区别。我需要先回忆一下这两个概念的基本定义和它们在Spark中的作用。首先,transformation是用来转换数据的,比如map、filter这些操作,而action则是触发实际计算的,比如count、collect之类的。 接下来,我得想用户可能的使用场景。可能他们刚开始学Spark,或者在写代码的时候遇到了性能问题,比如执行时间太长或者数据没有按预期处理。这时候理解两者的区别很重要,因为transformation是惰性的,只有action才会真正执行,这可能会影响代码的优化和调试。 用户的需求可能不仅仅是知道定义,而是想了解如何正确使用它们来优化作业,避免不必要的计算或者内存问题。比如,过多的action可能导致重复计算,影响效率。或者他们可能遇到了DAG执行的问题,需要明白为什么某些转换没有立即生效。 还要考虑用户可能的误区,比如认为transformation会立即修改数据,但实际上只是记录了操作。这时候需要强调惰性求值的概念,以及DAG的执行流程。同时,action会触发作业的提交,可能需要提到资源分配和任务调度的影响。 另外,举例说明会有帮助。比如,用map和reduce的例子,或者常见的action如take、saveAsTextFile。同时,要提到持久化(persist/cache)的作用,避免重复计算,特别是当多个action用到同一个transformation结果时。 需要确保回答结构清晰,分点说明区别、原理、使用注意事项。可能还要提醒用户注意transformation的链式调用和action的触发点,以及如何通过合理设计来优化Spark应用性能。最后,总结两者的核心差异,帮助用户形成系统的理解。</think>### Spark Action算子与Transformation算子区别详解 --- #### **1. 核心定义** - **Transformation(转换算子)** - 用于**构建数据处理逻辑**(如过滤、映射、聚合等),生成新的RDD/Dataset。 - **示例**:`map()`, `filter()`, `groupByKey()`, `join()` - **特性**:**惰性执行**,仅记录操作逻辑,不立即计算。 - **Action(行动算子)** - 用于**触发实际计算**并返回结果(或保存结果到外部系统)。 - **示例**:`count()`, `collect()`, `saveAsTextFile()`, `foreach()` - **特性**:**立即执行**,触发所有关联的Transformation计算。 --- #### **2. 核心区别** | **维度** | **Transformation** | **Action** | |-------------------|------------------------------------|--------------------------------| | **执行时机** | 延迟执行(记录操作到DAG) | 立即执行(触发DAG计算) | | **返回值** | 返回新的RDD/Dataset | 返回非RDD值(如数值、集合等) | | **数据操作** | 定义数据处理流程 | 触发实际计算并输出结果 | | **依赖关系** | 构建DAG中的节点 | 触发DAG提交到集群执行 | --- #### **3. 执行原理** - **Transformation的惰性机制** - Spark会记录所有Transformation操作,生成一个**有向无环图(DAG)**。 - 例如: ```scala val rdd = sc.textFile("data.txt") // Transformation .filter(_.contains("error")) // Transformation .map(_.toUpperCase()) // Transformation ``` *此时仅构建DAG,无实际计算。* - **Action触发计算** - 调用Action时,Spark会根据DAG生成物理执行计划,划分Stage和Task,提交到集群执行。 - 例如: ```scala rdd.count() // Action:触发计算并返回结果 ``` *此时才会读取文件、过滤数据、映射转换,最终统计行数。* --- #### **4. 常见误区与注意事项** 1. **重复计算问题** - 多个Action使用同一个Transformation结果时,会重复计算。 - **优化方法**:使用`persist()`或`cache()`缓存中间结果。 2. **数据溢出风险** - 如`collect()`会将所有数据拉取到Driver内存,可能导致OOM。 - **替代方案**:使用`take(N)`或写入外部存储系统。 3. **DAG优化机制** - Spark会自动合并连续的Transformation(如多个`map`操作合并为单次遍历)。 --- #### **5. 总结** - **Transformation**:定义“做什么”,是计算逻辑的蓝图。 - **Action**:定义“何时做”,是实际计算的触发器。 - **关键设计思想**:通过惰性求值优化执行计划,减少不必要的计算。 **实际开发建议**:合理规划Action调用频率,避免重复计算,优先使用窄依赖Transformation提升并行效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值