一、 十个转换算子(补充细节)
mapvsmapPartitions:map处理单条数据;mapPartitions处理一个分区的迭代器。(注意:如果用mapPartitions把迭代器转成 List,容易 OOM)。mapValues:只处理(K, V)中的 V,不改变 Key。底层不触发 Shuffle,属于窄依赖。coalescevsrepartition(重点考点):coalesce默认shuffle = false,是窄依赖,只能用来减少分区(把多个小分区捏成一个大分区)。repartition强制shuffle = true,是宽依赖,既能增也能减分区,但一定会触发全量数据网络洗牌。
sortByKey:底层是RangePartitioner,必须触发 Shuffle,它是全局排序(分区内有序,且分区之间有范围大小关系)。
二、 高效算子(补充底层细节)
1. reduceByKey / aggregateByKey(Map 端预聚合)
- 细节:为什么快?因为在 Shuffle Write(落盘)之前,Spark 会先用一个
HashMap在 Map 端(本地)把相同 Key 的数据先合并一次(这叫mapSideCombine = true)。 - 对比:
groupByKey源码里写死了mapSideCombine = false,所以它是“纯纯的无脑搬”,网络和磁盘 IO 极大。 aggregateByKey的坑:它的初始值zeroValue是每个分区每次遇到新 Key 都会执行一次,所以zeroValue必须是绝对的安全中性值(比如加法用 0,求最大值用极小值)。如果用求平均值,绝对不能直接用aggregateByKey一步到位,必须转成(sum, count)元组来算。
2. mapPartitions / foreachPartition(降调用次数)
- 细节:极大分摊了对象创建的开销(如 JDBC 连接)。
- 避坑:因为你处理的是一个分区的迭代器,如果在函数里写
iterator.toList,分区一大直接 OOM。所以必须用流式遍历(比如while(iterator.hasNext)),做到“处理一条、丢一条”,内存占用极小。
3. filter + coalesce
- 细节:
filter剔除脏数据后,某些分区可能变空,导致后续 Task 有大量空跑。紧跟着用coalesce(窄依赖,不走 Shuffle),直接把变小的分区合并,减少后续 Task 数量。
三、 如何实现 distinct(补充优化绝杀)
- 底层原理:
map(x => (x, null)).reduceByKey((x, y) => x).map(_._1) - 面试绝杀(数据倾斜优化):
- 痛点:如果 RDD 里有很多
null或者""等大量重复的异常数据,distinct会把它们全部 Hash 到同一个 Reduce 节点,直接把该节点干崩(数据倾斜)。 - 解法:在去重前,先给数据加随机前缀(加盐),打散后再做
reduceByKey去重,最后去掉前缀再map一次。或者用mapPartitions内部用HashSet先做一次局部去重。
- 痛点:如果 RDD 里有很多
四、 控制算子(补充必考陷阱)
cachevspersist:cache()底层调用的就是persist(MEMORY_ONLY)。它们都是懒执行,必须遇到 Action 才触发。checkpoint的坑:- 一定要在
checkpoint()之前加上persist()。否则,写 HDFS 触发一次计算,后续 Action 又触发一次计算,血统重算了两次。 - 切断血统:
checkpoint写完 HDFS 后,原来的 RDD 血统(Lineage)会被直接清空,彻底摆脱对父 RDD 的依赖。 - 生命周期:
cache是随 App 结束而释放;checkpoint是永久存在(除非手动删 HDFS)。 - 丢失风险:
cache数据丢了会顺着血统重算;checkpoint数据丢了(极其罕见),Spark 会报错停止。
- 一定要在
五、 reduceByKey 和 groupByKey 有什么区别(补充源码细节)
- 源码参数:
reduceByKey内部mapSideCombine = true;groupByKey内部mapSideCombine = false。 - 类型限制:
reduceByKey要求输入输出类型一致(比如都是 Int);aggregateByKey允许输入输出类型不一致。 groupByKey的唯一保留地:当你不仅需要聚合,还需要拿到这个 Key 对应的所有原始 Value 的明细集合时(比如查某用户的所有操作日志),才使用groupByKey。其他情况一律用reduceByKey。
本文深入解析Spark十大转换算子的语义与依赖关系,重点对比map/mapPartitions、reduceByKey/groupByKey等关键算子的底层机制;详解mapPartitionsWithIndex、foreachPartition等高效算子的内存与IO优化原理;剖析distinct的数据倾斜优化方案;阐明cache/persist的血统管理、存储级别与生命周期差异;并从源码层面辨析aggregateByKey与foldByKey的类型约束与适用场景。
650

被折叠的 条评论
为什么被折叠?



