在hadoop中Job提交的流程

java 复制代码
/**
 * 表示一个MapReduce作业,并提供了向集群提交作业、跟踪其进度以及控制执行的方法。
 * 这是用户与MapReduce框架交互的主要入口点。
 */
public class Job implements JobContext {
    
    /**
     * 提交作业至集群,并等待其完成。
     * 此方法是作业执行的最终触发点,它封装了完整的提交、执行和监控生命周期。
     *
     * @param verbose 是否在控制台打印详细的进度和状态信息。
     * @return {@code true} 如果作业成功完成;{@code false} 如果作业失败。
     * @throws IOException 如果在提交或监控过程中发生I/O异常。
     * @throws ClassNotFoundException 如果找不到作业所需的类。
     * @throws InterruptedException 如果等待过程被中断。
     */
    public boolean waitForCompletion(boolean verbose) throws IOException,
                                           InterruptedException, ClassNotFoundException {
        // 1. 提交作业到集群。如果提交失败,则立即返回false。
        if (state == JobState.DEFINE) {
            // 内部调用 submit() 方法
            submit();
        }
        
        // 2. 作业提交后,进入监控循环。
        if (verbose) {
            // 监控循环:定期从集群拉取作业的最新状态和进度。
            while (!isComplete()) {
                // 2.1 打印进度信息,包括Map和Reduce的完成百分比。
                log.info("Map: " + getMapProgress() + " Reduce: " + getReduceProgress());
                // 2.2 线程睡眠一段时间(例如1秒)后再次检查,避免过度频繁查询。
                Thread.sleep(1000);
            }
        } else {
            // 非详细模式:静默等待,直到作业完成(成功或失败)。
            while (!isComplete()) {
                Thread.sleep(1000);
            }
        }
        // 3. 返回作业的最终状态(成功与否)。
        return isSuccessful();
    }

    /**
     * 提交作业到MapReduce集群。
     * 这是提交流程的公开入口,内部会进行状态检查并触发实际的提交逻辑。
     *
     * @throws IOException 如果在提交过程中发生I/O异常。
     * @throws InterruptedException 如果操作被中断。
     * @throws ClassNotFoundException 如果找不到作业配置的类。
     */
    public void submit() throws IOException, InterruptedException, ClassNotFoundException {
        // 确保作业处于"已定义"状态,避免重复提交。
        ensureState(JobState.DEFINE);
        
        // 1.1.1 建立与集群的连接。此步骤至关重要,它决定了作业的运行环境(本地/Yarn)。
        // 创建一个 Cluster 对象,该对象作为与集群交互的代理。
        setUseNewAPI();
        connect();
        
        // 获取一个提交器(Submitter)实例,该实例包含了提交作业的核心内部逻辑。
        final JobSubmitter submitter = getJobSubmitter(cluster.getFileSystem(), cluster.getClient());
        
        // 1.1.2 调用内部提交方法,执行繁重的提交工作。
        status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>() {
            public JobStatus run() throws IOException, InterruptedException, ClassNotFoundException {
                return submitter.submitJobInternal(Job.this, cluster);
            }
        });
        
        // 提交成功后,将作业状态更新为"运行中"。
        state = JobState.RUNNING;
    }

    /**
     * 建立与MapReduce集群的连接。
     * 通过分析配置,初始化一个 Cluster 实例,该实例抽象了底层的运行环境。
     *
     * @throws IOException 如果连接失败。
     * @throws InterruptedException 如果连接过程被中断。
     */
    private void connect() throws IOException, InterruptedException {
        // 创建 Cluster 对象。在构造函数中,会调用 initialize 方法来确定运行环境。
        // 关键参数:mapreduce.framework.name (设置为 'yarn' 或 'local')
        cluster = Cluster.getInstance(getConfiguration());
    }

    // --- 内部类 JobSubmitter 的核心方法 submitJobInternal 详解 ---

    /**
     * JobSubmitter 类的核心方法,负责将作业的所有必要资源准备并提交到集群。
     * 这是整个提交过程中最复杂、最关键的步骤。
     *
     * @param job 要提交的Job对象。
     * @param cluster 代表集群连接的Cluster对象。
     * @return 作业的初始状态(JobStatus)。
     * @throws IOException 如果在准备或提交文件时发生I/O异常。
     * @throws InterruptedException 如果操作被中断。
     * @throws ClassNotFoundException 如果作业配置的InputFormat等类找不到。
     */
    JobStatus submitJobInternal(Job job, Cluster cluster) throws IOException, InterruptedException, ClassNotFoundException {

        // 1.1.2.1 输出检查:确保作业配置正确且安全。
        checkSpecs(job);
        
        // 1.1.2.2 创建Staging目录:在集群文件系统(HDFS或本地)上为本次作业创建临时工作区。
        // 路径示例(Yarn on HDFS):/user/<username>/.staging
        Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);

        // 1.1.2.3 获取JobID:向集群申请一个全局唯一的作业标识符。
        // 格式通常为:job_<集群启动时间戳>_<序列号> (e.g., job_1687891234567_0001)
        JobID jobId = submitClient.getNewJobID();
        
        // 1.1.2.4 创建Job专属提交路径:在Staging目录下,以JobID命名创建子目录。
        // 路径示例:/user/<username>/.staging/job_1687891234567_0001/
        Path submitJobDir = new Path(jobStagingArea, jobId.toString());

        // 1.1.2.5 拷贝资源到集群:将作业运行所需的JAR包、依赖库、配置文件等上传到提交目录。
        // 这包括:
        //   - 作业的JAR包(如果存在)
        //   - 通过 -libjars 添加的第三方JAR
        //   - 通过 -files 添加的分布式缓存文件
        //   - 通过 -archives 添加的归档文件
        copyAndConfigureFiles(job, submitJobDir);
        
        // 定义变量来保存计算出的切片数量,这对决定需要启动多少个Map任务至关重要。
        int maps;
        
        // 1.1.2.6 计算切片并生成切片规划文件:这是并行度的基础。
        try {
            // writeSplits 方法内部会调用 writeNewSplits
            maps = writeSplits(job, submitJobDir);
        }
        // ... 异常处理

        // 将计算出的Map任务数量正式设置到作业配置中。
        conf.setInt(MRJobConfig.NUM_MAPS, maps);

        // 1.1.2.7 写入作业配置文件:将当前的Configuration对象序列化为XML文件(job.xml)并上传到提交目录。
        // 这个文件包含了作业运行所需的所有参数,将在任务运行时被分布式加载。
        writeConf(conf, submitJobFile);

        // 1.1.2.8 正式提交作业:调用集群客户端(LocalJobRunner 或 YARNRunner)的提交方法。
        // 此时,所有准备工作已完成,集群开始接管作业的执行。
        status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());
        return status;
    }

    /**
     * 检查作业的输出规范是否合法。
     *
     * @param job 要检查的作业。
     * @throws IOException 如果输出路径未设置或已存在。
     */
    private void checkSpecs(Job job) throws IOException {
        // 检查1:输出路径是否已设置。
        OutputFormat<?, ?> output = ReflectionUtils.newInstance(job.getOutputFormatClass(), conf);
        output.checkOutputSpecs(job);
        // OutputFormat的checkOutputSpecs实现通常会:
        //   - 确保 job.getOutputPath() 不为空。
        //   - 确保 job.getOutputPath() 指向的路径在文件系统中不存在,防止数据被意外覆盖。
    }

    /**
     * 计算输入数据的切片,并生成切片信息文件。
     *
     * @param job 当前作业。
     * @param jobSubmitDir 作业提交目录。
     * @return 计算出的切片数量(即Map任务数量)。
     * @throws IOException 如果计算切片或写入文件时发生I/O错误。
     * @throws InterruptedException 如果操作被中断。
     */
    private int writeNewSplits(Job job, Path jobSubmitDir) throws IOException, InterruptedException {
        // 通过反射实例化用户在作业中配置的 InputFormat 类(如 TextInputFormat, KeyValueTextInputFormat)。
        InputFormat<?, ?> input = ReflectionUtils.newInstance(job.getInputFormatClass(), conf);

        // 核心调用:获取输入数据的逻辑切片列表。
        // 此方法会调用具体InputFormat的getSplits方法,其逻辑大致如下:
        // 1. 获取输入文件列表。
        // 2. 对于每个文件:
        //    a. 获取文件长度(fs.getFileStatus(path).getLen())。
        //    b. 根据配置的切片大小(mapreduce.input.fileinputformat.split.maxsize)计算文件需要被切分成几块。
        //    c. 每个切片(FileSplit)包含以下元信息:
        //        - 文件路径
        //        - 切片在文件中的起始偏移量
        //        - 切片长度
        //        - 切片所在的宿主(位置信息,用于优化调度)
        List<InputSplit> splits = input.getSplits(job);
        
        // 将切片信息序列化到提交目录下的 'job.split' 文件中。
        // 同时,将切片的元数据信息写入 'job.splitmetainfo' 文件以供快速访问。
        JobSplitWriter.createSplitFiles(jobSubmitDir, conf, jobSubmitDir.getFileSystem(conf), splits);

        // 返回切片数量,这决定了集群需要启动多少个Map任务。
        return splits.size();
    }

    /**
     * 将作业的配置信息写入XML文件。
     *
     * @param conf 作业的配置对象。
     * @param confFile 配置文件的目标路径(通常是提交目录下的 'job.xml')。
     * @throws IOException 如果写入文件失败。
     */
    private void writeConf(Configuration conf, Path confFile) throws IOException {
        // 创建一个指向 confFile 的 FSDataOutputStream。
        // 将Configuration对象中的所有键值对以XML格式序列化到该输出流中。
        // 这个 job.xml 文件将被分发到每个Map和Reduce任务节点,任务通过加载此文件来获取运行参数。
        conf.writeXml(out);
    }
}

/**
 * 代表一个MapReduce集群。根据配置,它可能是本地集群(LocalJobRunner)或远程Yarn集群(YARNRunner)。
 * 这个类封装了与特定集群环境交互的细节。
 */
public class Cluster {
    
    /**
     * 根据配置初始化集群客户端。
     *
     * @param conf 作业配置。
     * @throws IOException
     * @throws InterruptedException
     */
    protected void initialize(InetSocketAddress jobTrackAddr, Configuration conf) throws IOException, InterruptedException {
        // 决策逻辑:
        // 读取 conf.get(MRConfig.FRAMEWORK_NAME) 的值。
        // 如果值为 "local" -> 创建 LocalJobRunner 客户端。
        //    - LocalJobRunner: 在单个JVM进程中顺序执行Map和Reduce任务,用于本地测试和调试。
        // 如果值为 "yarn" -> 创建 YARNRunner 客户端。
        //    - YARNRunner: 与Yarn ResourceManager通信,将作业作为Application提交到Yarn集群上执行。
        this.client = clientProvider.createClient(jobTrackAddr, conf);
    }
    
    // ... 其他方法,如 getNewJobID(), submitJob() 等,实际委托给具体的 client (LocalJobRunner/YARNRunner) 执行。
}

总结:

  1. 环境探测与连接建立 :首先确定作业的运行环境(本地或Yarn),并建立相应的连接代理(Cluster)。

  2. 安全性与规范性检查:检查输出路径,防止数据覆盖等错误。

  3. 资源准备与上传:在集群文件系统上创建工作目录(Staging Area),并将作业的JAR包、依赖库、配置文件等资源上传至此。

  4. 并行度规划(核心) :通过用户指定的 InputFormat 对输入数据进行逻辑切片(Splitting),切片数量直接决定了Map任务的数量,这是MapReduce并行计算的基石。

  5. 配置序列化:将作业的所有配置参数写入XML文件,以便在任务运行时被分发和加载。

  6. 最终提交与执行 :将所有准备就绪的文件路径等信息传递给集群客户端(LocalJobRunner/YARNRunner),由集群正式接管并调度执行该作业。

  7. 持续监控:作业提交后,客户端进入监控循环,不断从集群获取状态和进度,直到作业完成。

相关推荐
编啊编程啊程5 小时前
Netty从0到1系列之RPC通信
java·spring boot·rpc·kafka·dubbo·nio
召摇5 小时前
Java Web开发从零开始:初学者完整学习指南
java·后端·面试
程序猿不脱发25 小时前
Redis 内存淘汰策略 LRU 和传统 LRU 差异
java·后端·spring
王大锤43915 小时前
2种方式从springbean中获取bean实例
java·spring boot
mumu1307梦5 小时前
SpringAI 实战:解决 Netty 超时问题,优化 OpenAiApi 配置
java·spring boot·netty·超时·timeout·openapi·springai
咖啡Beans5 小时前
了解Mybatis拦截器
java·spring boot·mybatis
SimonKing5 小时前
Xget:又一下载神器诞生!开源免费无广告,速度拉满!
java·后端·程序员
冬天vs不冷5 小时前
Java基础(十四):枚举类详解
android·java·python
兜兜风d'5 小时前
jetbrain插件市场无法下载插件/idea插件install无效/idea无法安装插件
java·ide·intellij-idea