【从零开始学习Spark | 第一篇】:算子与持久化

本文深入解析Spark十大转换算子的语义与依赖关系,重点对比map/mapPartitions、reduceByKey/groupByKey等关键算子的底层机制;详解mapPartitionsWithIndex、foreachPartition等高效算子的内存与IO优化原理;剖析distinct的数据倾斜优化方案;阐明cache/persist的血统管理、存储级别与生命周期差异;并从源码层面辨析aggregateByKey与foldByKey的类型约束与适用场景。

一、 十个转换算子(补充细节)

  • map vs mapPartitions:map 处理单条数据;mapPartitions 处理一个分区的迭代器。(注意:如果用 mapPartitions 把迭代器转成 List,容易 OOM)。
  • mapValues:只处理 (K, V) 中的 V,不改变 Key。底层不触发 Shuffle,属于窄依赖。
  • coalesce vs repartition(重点考点):
    • 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 先做一次局部去重。

四、 控制算子(补充必考陷阱)

  • cache vs persist: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。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值