一、 十个转换算子补充细节mapvsmapPartitionsmap处理单条数据mapPartitions处理一个分区的迭代器。注意如果用mapPartitions把迭代器转成 List容易 OOM。mapValues只处理(K, V)中的 V不改变 Key。底层不触发 Shuffle属于窄依赖。coalescevsrepartition重点考点coalesce默认shuffle false是窄依赖只能用来减少分区把多个小分区捏成一个大分区。repartition强制shuffle true是宽依赖既能增也能减分区但一定会触发全量数据网络洗牌。sortByKey底层是RangePartitioner必须触发 Shuffle它是全局排序分区内有序且分区之间有范围大小关系。二、 高效算子补充底层细节1.reduceByKey/aggregateByKeyMap 端预聚合细节为什么快因为在 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.filtercoalesce细节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先做一次局部去重。四、 控制算子补充必考陷阱cachevspersistcache()底层调用的就是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 truegroupByKey内部mapSideCombine false。类型限制reduceByKey要求输入输出类型一致比如都是 IntaggregateByKey允许输入输出类型不一致。groupByKey的唯一保留地当你不仅需要聚合还需要拿到这个 Key 对应的所有原始 Value 的明细集合时比如查某用户的所有操作日志才使用groupByKey。其他情况一律用reduceByKey。
阅读完成 · 觉得有帮助?