mapreduce.fileoutputcommitter.algorithm.version

两个版本(各有优劣)

  • mapreduce.fileoutputcommitter.algorithm.version = 1
    性能方面:v1在task结束后只是将输出文件拷到临时目录,然后在job结束后才由Driver把这些文件再拷到输出目录。如果文件数量很多,Driver就需要不断的和NameNode做交互,而且这个过程是单线程的,因此势必会增加耗时。如果我们碰到有spark任务所有task结束了但是任务还没结束,很可能就是Driver还在不断的拷文件;

    数据一致性方面:v1在Job结束后才批量拷文件,其实就是两阶段提交,它可以保证数据要么全部展示给用户,要么都没展示(当然,在拷贝过程中也无法保证完全的数据一致性,但是这个时间一般来说不会太长)。如果任务失败,也可以直接删了_temporary目录,可以较好的保证数据一致性。

  • mapreduce.fileoutputcommitter.algorithm.version = 2
    性能方面:v2在task结束后立马将输出文件拷贝到输出目录,后面Job结束后Driver就不用再去拷贝了

    数据一致性方面: v2在task结束后就拷文件,就会造成spark任务还未完成就让用户看到一部分输出,这样就完全没办法保证数据一致性了。另外,如果任务在输出过程中失败,就会有一部分数据成功输出,一部分没输出的情况。

  • 总结
    在性能方面,v2完胜v1
    在数据一致性方面,v1完胜v2

原理解析

Spark任务输出文件的总过程

  • Job启动时创建一个目录: ${output.dir}/_temporary/${appAttemptId} 作为本次运行的输出临时目录
  • 当有task开始运行后,会创建 ${output.dir}/_temporary/${appAttemptId}/_temporary/${taskAttemptId}/${fileName} 文件,后面这个task的所有输出都会被写到这个文件中
  • 当task运行完后,需要检查是否要commit,如果需要commit,会调用OutputCommitter#commitTask()方法
  • 等整个Job执行完就调用OutputCommitter#commitJob()方法

ps: 上述路径解释
output.dir表示用户指定的输出目录,appAttemptId表示任务的attemptId,一般从0开始一直递增。taskAttemptId表示task的attemptId,比如taskId是0,第一次运行,这个id就是0.0。taskAttemptId的输出格式是这样的(toString方法实现)

1、
当task的type是map时,格式为attempt_{时间表达字符串}{stageId}m{sparkPartitionId}{TaskId},比如attempt目录是attempt_20190801032358_0020_m_000093_7683,则表示stageId为20,type是m,对应map
task(reduce
task则为r),sparkPartitionId表示该task在stage中的id是93,最后,它对应的TaskId是7683。

2、
当task的type是reduce时,格式为attempt_{时间表达字符串}{rddId}r{sparkPartitionId}{attemptNumber},比如attempt目录是attempt_20190801042010_1478_r_000970_1,则表示对应的rddId是1478,r表示type是reduce
task,970表示partiton的Id,最后的1表示这是该task的第二次运行(第一次运行是0)——
一个stage中可能会有多个rdd,所以stageId并不等于rddId

mapreduce.fileoutputcommitter.algorithm.version 控制 commitTask和commitJob的具体方式

OutputCommitter 只是一个抽象类,spark运行时会从配置中获取指定的实现类,如果配置中没指定,spark默认会使用 org.apache.hadoop.mapred.FileOutputCommitter 的实现。

FileOutputCommitter 源码

public void commitTask(TaskAttemptContext context, Path taskAttemptPath) 
    throws IOException {
 
     ........
 
    if (taskAttemptDirStatus != null) {
      if (algorithmVersion == 1) {
        Path committedTaskPath = getCommittedTaskPath(context);
        if (fs.exists(committedTaskPath)) {
           if (!fs.delete(committedTaskPath, true)) {
             throw new IOException("Could not delete " + committedTaskPath);
           }
        }
        if (!fs.rename(taskAttemptPath, committedTaskPath)) {
          throw new IOException("Could not rename " + taskAttemptPath + " to "
              + committedTaskPath);
        }
        LOG.info("Saved output of task '" + attemptId + "' to " +
            committedTaskPath);
      } else {
        // directly merge everything from taskAttemptPath to output directory
        mergePaths(fs, taskAttemptDirStatus, outputPath);
        LOG.info("Saved output of task '" + attemptId + "' to " +
            outputPath);
      }
    } else {
      LOG.warn("No Output found for " + attemptId);
    }
  } else {
    LOG.warn("Output Path is null in commitTask()");
  }
}
 
public void commitJob(JobContext context) throws IOException {
      ........
      jobCommitNotFinished = false;
     ........
}
 
protected void commitJobInternal(JobContext context) throws IOException {
    ........
    if (algorithmVersion == 1) {
      for (FileStatus stat: getAllCommittedTaskPaths(context)) {
        mergePaths(fs, stat, finalOutput);
      }
    }
   ........
}
  • 设置成v1时,task完成commitTask时会将临时生成的数据移动到task对应目录下,当所有task都执行完成进行commitJob的时候,会由driver按照单线程模式将所有task目录下的数据文件移动到最终的作业输出目录

v1:commitTask的操作是将 ${output.dir}/_temporary/${appAttemptId}/_temporary/${taskAttemptId} 重命名为 ${output.dir}/_temporary/${appAttemptId}/${taskId})

  • 设置成v2时,task完成就会直接将临时生成的数据移动到Job最终输出目录,commitJob的时候就不需要再次移动数据

v2: commitTask的操作是将 ${output.dir}/_temporary/${appAttemptId}/_temporary/${taskAttemptId} 下的文件移动到 ${output.dir} 目录下 (也就是最终的输出目录)

设置方法

  • spark任务可以通过设置spark配置 spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version=2来开启版本2的commit逻辑

  • 在hadoop 2.7.0之前,FileOutputCommitter的实现没有区分版本,统一都是使用version=1的commit逻辑。因此如果spark的hadoop依赖包版本如果低于2.7.0,设置mapreduce.fileoutputcommitter.algorithm.version=2是没有用的

  • spark运行时会从配置中获取指定的实现类,如果配置中没指定,spark默认会使用 org.apache.hadoop.mapred.FileOutputCommitter 的实现。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值