Spark常用算子的实现原理

💐💐扫码关注公众号,回复 spark 关键字下载geekbang 原价 90 元 零基础入门 Spark 学习资料💐💐

mapPartitions:以数据分区为粒度的数据转换

mapPartitions 以数据分区(匿名函数的形参 partition)为粒度,对 RDD 进行数据转换。具体的数据处理逻辑,则由代表数据分区的形参 partition 进一步调用 map(f) 来完成。以数据分区为单位,实例化对象的操作只需要执行一次,而同一个数据分区中所有的数据记录,都可以共享该对象。对于数据记录来说,凡是可以共享的操作,都可以用 mapPartitions 算子进行优化。这样的共享操作还有很多,比如创建用于连接远端数据库的 Connections 对象,或是用于连接 Amazon S3 的文件系统句柄,再比如用于在线推理的机器学习模型,等等,不一而足。

groupByKey:分组收集

为了完成分组收集,对于 Key 值相同、但分散在不同数据分区的原始数据记录,Spark 需要通过 Shuffle 操作,跨节点、跨进程地把它们分发到相同的数据分区。Shuffle 是资源密集型计算,对于动辄上百万、甚至上亿条数据记录的 RDD 来说,这样的 Shuffle 计算会产生大量的磁盘 I/O 与网络 I/O 开销,从而严重影响作业的执行性能。

reduceByKey:分组聚合

尽管 reduceByKey 也会引入 Shuffle,但相比 groupByKey 以全量原始数据记录的方式消耗磁盘与网络,reduceByKey 在落盘与分发之前,会先在 Shuffle 的 Map 阶段做初步的聚合计算。

比如,在数据分区 0 的处理中,在 Map 阶段,reduceByKey 把 Key 同为 Streaming 的两条数据记录聚合为一条,聚合逻辑就是由函数 f 定义的、取两者之间 Value 较大的数据记录,这个过程我们称之为“Map 端聚合”。相应地,数据经由网络分发之后,在 Reduce 阶段完成的计算,我们称之为“Reduce 端聚合”。在工业级的海量数据下,相比 groupByKey,reduceByKey 通过在 Map 端大幅削减需要落盘与分发的数据量,往往能将执行效率提升至少一倍。reduceByKey 算子的局限性,在于其 Map 阶段与 Reduce 阶段的计算逻辑必须保持一致,这个计算逻辑统一由聚合函数 f 定义。当一种计算场景需要在两个阶段执行不同计算逻辑的时候,reduceByKey 就爱莫能助了。

coalesce

repartition 会引入 Shuffle,而 coalesce 不会。具体来说,给定任意一条数据记录,repartition 的计算过程都是先哈希、再取模,得到的结果便是该条数据的目标分区索引。对于绝大多数的数据记录,目标分区往往坐落在另一个 Executor、甚至是另一个节点之上,因此 Shuffle 自然也就不可避免。coalesce 则不然,在降低并行度的计算中,它采取的思路是把同一个 Executor 内的不同数据分区进行合并,如此一来,数据并不需要跨 Executors、跨节点进行分发,因而自然不会引入 Shuffle。

广播变量(Broadcast variables)

普通变量的痛点

如上图所示,list 变量本身是在 Driver 端创建的,它并不是分布式数据集(如 lineRDD、wordRDD)的一部分。因此,在分布式计算的过程中,Spark 需要把 list 变量分发给每一个分布式任务(Task),从而对不同数据分区的内容进行过滤。在这种工作机制下,如果 RDD 并行度较高、或是变量的尺寸较大,那么重复的内容分发就会引入大量的网络开销与存储开销,而这些开销会大幅削弱作业的执行性能。

Driver 端变量的分发是以 Task 为粒度的,系统中有多少个 Task,变量就需要在网络中分发多少次。更要命的是,每个 Task 接收到变量之后,都需要把它暂存到内存,以备后续过滤之用。换句话说,在同一个 Executor 内部,多个不同的 Task 多次重复地缓存了同样的内容拷贝,毫无疑问,这对宝贵的内存资源是一种巨大的浪费。RDD 并行度较高,意味着 RDD 的数据分区数量较多,而 Task 数量与分区数相一致,这就代表系统中有大量的分布式任务需要执行。如果变量本身尺寸较大,大量分布式任务引入的网络开销与内存开销会进一步升级。在工业级应用中,RDD 的并行度往往在千、万这个量级,在这种情况下,诸如 list 这样的变量会在网络中分发成千上万次,作业整体的执行效率自然会很差 。

广播变量的优势

在使用广播变量之前,list 变量的分发是以 Task 为粒度的,而在使用广播变量之后,变量分发的粒度变成了以 Executors 为单位,同一个 Executor 内多个不同的 Tasks 只需访问同一份数据拷贝即可。换句话说,变量在网络中分发与存储的次数,从 RDD 的分区数量,锐减到了集群中 Executors 的个数。要知道,在工业级系统中,Executors 个数与 RDD 并行度相比,二者之间通常会相差至少两个数量级。在这样的量级下,广播变量节省的网络与内存开销会变得非常可观,省去了这些开销,对作业的执行性能自然大有裨益。