在Shuffle Read阶段, 数据操作需要3个功能: 跨节点数据获取、 聚合和排序。
(1) 不需要聚合, 不需要按Key进行排序。
,等待所有的map task结束后, reduce task开始不断从各个map task获取< K, V> record, 并将record输出到一个buffer中(大小为spark.reducer.maxSizeInFlight=48MB) , 下一个操作直接从buffer中获取数据即可;
该Shuffle模式的优缺点: 优点是逻辑和实现简单, 内存消耗很小。
缺点是不支持聚合、 排序等复杂功能。该Shuffle模式适用的操作: 适合既不需要聚合也不需要排序的应用, 如partitionBy() 等。
(2) 不需要聚合, 需要按Key进行排序
需要实现数据获取和按Key排序的功能。 如图6.10所示, 获取数据后, 将buffer中的record依次输出到一个Array结构(PartitionedPairBuffer) 中。
由于这里采用了本来用于Shuffle Write端的PartitionedPairBuffer结构, 所以还保留了每个record的partitionId。 然后, 对Array中的record按照Key进行排序, 并将排序结果输出或者传递给下一步操作。
当内存无法存下所有的record时, PartitionedPairBuffer将record排序后spill到磁盘上, 最后将内存中和磁盘上的record进行全局排序, 得到最终排序后的record。
该Shuffle模式的优缺点: 优点是只需要一个Array结构就可以支持按照Key进行排序, Array大小可控, 而且具有扩容和spill到磁盘上的功能, 不受数据规模限制。
缺点是排序增加计算时延。
该Shuffle模式适用的操作: 适合reduce端不需要聚合, 但需要按Key进行排序的操作, 如sortByKey() 、 sortBy() 等。
(3) 需要聚合, 不需要或需要按Key进行排序。
在这种情况下, 需要实现按照Key进行聚合, 根据需要按Key进行排序的功能。 如图6.11的上图所示, 获取record后, Spark建立一个类似HashMap的数据结构(ExternalAppendOnlyMap) 对buffer中的record进行聚合, HashMap中的Key是record中的Key, HashMap中的Value是经过相同聚合函数(func() ) 计算后的结果。
在图6.11中, 聚合函数是sum() 函数, 那么Value中存放的是多个record对应Value相加后的结果。之后, 如果需要按照Key进行排序, 如图6.11的下图所示, 则建立一个Array结构, 读取HashMap中的record, 并对record按Key进行排序, 排序完成后, 将结果输出或者传递给下一步操作;
如果HashMap存放不下, 则会先扩容为两倍大小, 如果还存放不下, 就将HashMap中的record排序后spill到磁盘上。 此时, HashMap被清空, 可以继续对buffer中的record进行聚合。 如果内存再次不够用, 那么继续spill到磁盘上, 此过程可以重复多次。 当聚合完成以后, 将此时HashMap中的reocrd与磁盘上已排序的record进行再次聚合, 得到最终的record, 输出到分区文件中。
该Shuffle模式的优缺点:
优点是只需要一个HashMap和一个Array结构就可以支持reduce端的聚合和排序功能, HashMap 具有扩容和spill到磁盘上的功能, 支持小规模到大规模数据的聚合。 边获取数据边聚合,效率较高。
缺点是需要在内存中进行聚合, 内存消耗较大, 如果有数据spill到磁盘上, 还需要进行再次聚合。 另外, 经过HashMap聚合后的数据仍然需要拷贝到Array中进行排序, 内存消耗较大。 在实现中, Spark使用的HashMap是一个经过特殊优化的HashMap, 命名为ExternalAppendOnlyMap, 可以同时支持聚合和排序操作, 相当于HashMap和Array的合体;
该Shuffle模式适用的操作: 适合reduce端需要聚合、 不需要或需要按Key进行排序的操作, 如reduceByKey() 、 aggregateByKey() 等;
Shuffle Read框架需要执行的3个步骤是“数据获取→聚合→排序输出”。
如果应用中的数据操作不需要聚合, 也不需要排序, 那么获取数据后直接输出。
对于需要按Key进行排序的操作, Spark 使用基于Array的方法来对Key进行排序。 对于需要聚合的操作, Spark提供了基于HashMap的聚合方法, 同时可以再次使用Array来支持按照Key进行排序。
总体来讲, Shuffle Read框架使用的技术和数据结构与Shuffle Write过程类似, 而且由于不需要分区, 过程比Shuffle Write更为简单。 当然, 还有一些可优化的地方, 如聚合和排序如何进行统一来减少内存copy和磁盘I/O等,