1、在 Spark 中,不同stage、不同节点上的task 进行数据传递的过程通常称为 Shuffle 机制。Shuffle 解决的是如何将数据进行重新组织,使其能够在上游和下游 task 间进行传递和计算。
Shuffle 机制面临的挑战:计算的多样性。Shuffle 机制分为Shuffle write 和 Shuffle read 两个阶段,前一个阶段主要是解决上游 stage 输出数据的分区问题,后者主要是解决下游stage 从上游stage 获取数据、重新组织数据,并为后续操作提供数据的问题。
(1). 数据分区问题解决方案:数据分区个数与下游stage 个数相同。分区个数也可以由用户自定义,如 groupByKey(numPartitions), 一般地将 numPartitions 定义为集群中可用 cpu 个数的 1~2 倍,即上游stage 将数据划分成numPartitions份,下游 stage 启动numPartitions个 task 来处理数据。如果用户没有定义分区个数,则分区个数默认是 parent RDD 的最大个数。
(2). 如何对map task 输出的数据进行分区呢?解决方法是对每个map task 输出的 <K,V> record, 对每个record 的 key 计算 partitionId, 具有不同 partitionId 的数据划分到不同分区中。如果numPartitions 为2, 则可以使用 partitionId = Hash(key)%2 计算partitionId,根据 partitionId 的不同值将 record 输出到不同分区文件中。这种方法简单,容易实现,但不支持 Shuffle Write 端的 combine() 操作。
(3). 数据聚合问题:使用 HashMap 分两步实现。先将不同 task 中获取到的record 放入 HashMap 中,key 是 record 中的 key, Value 是 list(V)。然后,对于HashMap 中的 <K, list(V)> record, 调用 func 函数得到 <K, func(list<V>)> record。
两步聚合方案能够解决数据聚合的问题,优点是逻辑清晰、实现简单,缺点是强依赖 HashMap, 内存占用空间大,效率低。
其优化方案是,在将 record 放入 HashMap 时,同时进行 func() 操作,并更新相应的聚合结果。