spark源码分析-shuffle write

本文详细分析了Spark中Shuffle的write阶段,包括ShuffleMapTask的运行、三种ShuffleWriter(UnsafeShuffleWriter、BypassMergeSortShuffleWriter、SortShuffleWriter)的工作原理,重点阐述了SortShuffleWriter如何利用ExternalSorter进行数据排序、溢写和合并。通过ExternalSorter的内存管理和磁盘操作,确保数据按分区和key排序。shuffle write完成后,会将索引信息发送到MapOutputTrackerMaster,以便后续的shuffle read阶段拉取数据。
摘要由CSDN通过智能技术生成

Spark会将job划分为多个Stage,每个job会由多个ShuffleMapStage和一个ResultStage组成,然后每个Stage会由多个Task组成,Task数量和每个Stage的Partition的数量相同。每个Task任务由单独的线程执行,不同Stage的Task之间需要进行数据流动,并且下游Stage的Task会依赖上游Stage的多个Task,所以该过程需要将数据写入磁盘,并将属于下游Stage相同Task的数据汇总到一起,以供该Task进行拉取,该过程被称为Shuffle。
shuffle过程分为shuffle上游Stage的Task的写过程和shuffle下游Stage的Task的读过程。首先看一下写过程:
写过程发生在ShuffleMapTask的runTask方法

override def runTask(context: TaskContext): MapStatus = {
    ...

    var writer: ShuffleWriter[Any, Any] = null
    try {
        // 获取Spark的ShuffleManager(SortShuffleManager),通过ShuffleManager获取对应ShuffleWriter的具体实现子类,进行写shuffle数据的
      val manager = SparkEnv.get.shuffleManager
      writer = manager.getWriter[Any, Any](dep.shuffleHandle, partitionId, context)
      writer.write(rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]])
      writer.stop(success = true).get
    } catch {
      case e: Exception =>
        try {
          if (writer != null) {
            writer.stop(success = false)
          }
        } catch {
          case e: Exception =>
            log.debug("Could not stop writer", e)
        }
        throw e
    }
  }

Spark的ShuffleWriter总共有三个实现类:
UnsafeShuffleWriter:主要用于对序列化的二进制数据进行排序,避免使用java对象,减少内存使用和GC开销,而且不需要序列化和反序列化,排序数据只需要使用分区id、key和指向数据的内存地址,可以避免cache-miss,优化排序性能。
BypassMergeSortShuffleWriter:该类会同时打开R个shuffle文件进行写入,如果shuffle数据量太大会生成太多的分区,从而造成同时打开太多文件,所以主要用于分区数小于配置参数spark.shuffle.sort.bypassMergeThreshold值的shuffle。但是如果shuffle使用了map端聚合操作的,无论分区数大小都不会使用该类。
SortShuffleWriter:该ShuffleWriter会优化BypassMergeSortShuffleWriter写出shuffle文件数过多的问题,而且该类可以使用map端聚合优化。
接下来主要使用SortShuffleWriter进行shuffle write流程的分析,后边会对其余两个ShuffleWriter的分析。
首先看一下write()方法,该方法会将数据写到ExternalSorter中,ExternalSorter会将先将数据写入到内存中然后对数据进行排序,当内存中数据到达一定大小会先将数据写到临时文件中,然后ExternalSorter会将所有写出的临时文件和最后未写出的数据进行merge sort排序,最后将排序完的数据写出到一个数据文件、一个索引文件。

 override def write(records: Iterator[Product2[K, V]]): Unit = {
    sorter = if (dep.mapSideCombine) {
    // shuffle过程有map端聚合优化,所有构造的ExternalSorter类传入聚合方法
    // ExternalSorter类的K为数据的key的类型,V为数据的类型,C为用于数据排序过程中使用的比较器类型
      require(dep.aggregator.isDefined, "Map-side combine without Aggregator specified!")
      new ExternalSorter[K, V, C](
        context, dep.aggregator, Some(dep.partitioner), dep.keyOrdering, dep.serializer)
    } else {
    // 没有map端聚合优化,构造的ExternalSorter类的泛型的第二个、第三个类型相同
      new ExternalSorter[K, V, V](
        context, aggregator = None, Some(dep.partitioner), ordering = None, dep.serializer)
    }
    // 将数据写入排序器进行数据排序
    sorter.insertAll(records)

    // 生成shuffle结果的临时文件,最终数据文件名为"shuffle_" + shuffleId + "_" + mapId + "_" + reduceId + ".data",临时文件名为"shuffle_" + shuffleId + "_" + mapId + "_" + reduceId + ".data" + "." + uuid
    val output = shuffleBlockResolver.getDataFile(dep.shuffleId, mapId)
    val tmp = Utils.tempFileWith(output)
    try {
      val blockId = ShuffleBlockId(dep.shuffleId, mapId, IndexShuffleBlockResolver.NOOP_REDUCE_ID)
      // 写排好序的shuffle数据到临时文件
      val partitionLengths =
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值