• MapReduce源码分析之JobSubmitter(一)


     JobSubmitter,顾名思义,它是MapReduce中作业提交者,而实际上JobSubmitter除了构造方法外,对外提供的唯一一个非private成员变量或方法就是submitJobInternal()方法,它是提交Job的内部方法,实现了提交Job的所有业务逻辑。本文,我们将深入研究MapReduce中用于提交Job的组件JobSubmitter。

            首先,我们先看下JobSubmitter的类成员变量,如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. // 文件系统FileSystem实例  
    2. private FileSystem jtFs;  
    3. // 客户端通信协议ClientProtocol实例  
    4. private ClientProtocol submitClient;  
    5. // 提交作业的主机名  
    6. private String submitHostName;  
    7. // 提交作业的主机地址  
    8. private String submitHostAddress;  

            它一共有四个类成员变量,分别为:

            1、文件系统FileSystem实例jtFs:用于操作作业运行需要的各种文件等;

            2、客户端通信协议ClientProtocol实例submitClient:用于与集群交互,完成作业提交、作业状态查询等;

            3、提交作业的主机名submitHostName;

            4、提交作业的主机地址submitHostAddress。

            其中,客户端通信协议ClientProtocol实例submitClient是通过Cluster的客户端通信协议ClientProtocol实例client来赋值的,我们在《MapReduce源码分析之新API作业提交(二):连接集群》一文中曾经提到过,它根据MapReduce中参数mapreduce.framework.name的配置为yarn或local,有Yarn模式的YARNRunner和Local模式的LocalJobRunner两种情况。
            接下来,我们再看下JobSubmitter的构造函数,如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1.  JobSubmitter(FileSystem submitFs, ClientProtocol submitClient)   
    2.  throws IOException {  
    3.     
    4. // 根据入参赋值成员变量submitClient、jtFs  
    5.    this.submitClient = submitClient;  
    6.    this.jtFs = submitFs;  
    7.  }  

            很简单,根据入参赋值成员变量submitClient、jtFs而已。

            关键的来了,我们看下JobSubmitter唯一的对外核心功能方法submitJobInternal(),它被用于提交作业至集群,代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1.  /** 
    2.   * Internal method for submitting jobs to the system. 
    3.   *  
    4.   * <p>The job submission process involves: 
    5.   * <ol> 
    6.   *   <li> 
    7.   *   Checking the input and output specifications of the job. 
    8.   *   </li> 
    9.   *   <li> 
    10.   *   Computing the {@link InputSplit}s for the job. 
    11.   *   </li> 
    12.   *   <li> 
    13.   *   Setup the requisite accounting information for the  
    14.   *   {@link DistributedCache} of the job, if necessary. 
    15.   *   </li> 
    16.   *   <li> 
    17.   *   Copying the job's jar and configuration to the map-reduce system 
    18.   *   directory on the distributed file-system.  
    19.   *   </li> 
    20.   *   <li> 
    21.   *   Submitting the job to the <code>JobTracker</code> and optionally 
    22.   *   monitoring it's status. 
    23.   *   </li> 
    24.   * </ol></p> 
    25.   * @param job the configuration to submit 
    26.   * @param cluster the handle to the Cluster 
    27.   * @throws ClassNotFoundException 
    28.   * @throws InterruptedException 
    29.   * @throws IOException 
    30.   */  
    31.  JobStatus submitJobInternal(Job job, Cluster cluster)   
    32.  throws ClassNotFoundException, InterruptedException, IOException {  
    33.   
    34.    //validate the jobs output specs   
    35. // 调用checkSpecs()方法,校验作业输出路径是否配置,且是否已存在,  
    36. // 正确的情况应该是已配置且未存在,输出路径配置参数为mapreduce.output.fileoutputformat.outputdir,  
    37. // 之前WordCount作业的输出路径配置为hdfs://nameservice1/output/output  
    38.    checkSpecs(job);  
    39.   
    40.    // 从作业job中获取配置信息conf  
    41.    Configuration conf = job.getConfiguration();  
    42.      
    43.    // 调用addMRFrameworkToDistributedCache()方法添加应用框架路径到分布式缓存中  
    44.    addMRFrameworkToDistributedCache(conf);  
    45.   
    46.    // 通过JobSubmissionFiles的getStagingDir()静态方法获取作业执行时阶段区域路径jobStagingArea  
    47.    // 取参数yarn.app.mapreduce.am.staging-dir,参数未配置默认为/tmp/hadoop-yarn/staging  
    48. // 然后后面是/提交作业用户名/.staging  
    49. // 通过之前的WordCount任务的执行,我们查看历史记录,得知参数yarn.app.mapreduce.am.staging-dir配置的为/user,  
    50. // 而提交作业用户名为hdfs,所以完整的路径应该为/user/hdfs/.staging  
    51.    Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);  
    52.      
    53.    //configure the command line options correctly on the submitting dfs  
    54.    // 获取当前本机地址  
    55.    InetAddress ip = InetAddress.getLocalHost();  
    56.      
    57.    // 确定提交作业的主机地址、主机名,并设置入配置信息conf,对应参数分别为  
    58.    // mapreduce.job.submithostname  
    59.    // mapreduce.job.submithostaddress  
    60.    if (ip != null) {  
    61.      submitHostAddress = ip.getHostAddress();  
    62.      submitHostName = ip.getHostName();  
    63.      conf.set(MRJobConfig.JOB_SUBMITHOST,submitHostName);  
    64.      conf.set(MRJobConfig.JOB_SUBMITHOSTADDR,submitHostAddress);  
    65.    }  
    66.      
    67.    // 生成作业ID,即JobID实例jobId  
    68.    JobID jobId = submitClient.getNewJobID();  
    69.      
    70.    // 将jobId设置入job  
    71.    job.setJobID(jobId);  
    72.      
    73.    // 构造提交作业路径Path实例submitJobDir,jobStagingArea后接/jobId,比如/job_1459913635503_0005  
    74.    // 之前WordCount作业的完整路径为/user/hdfs/.staging/job_1459913635503_0005  
    75.    Path submitJobDir = new Path(jobStagingArea, jobId.toString());  
    76.    JobStatus status = null;  
    77.      
    78.    // 设置作业一些参数:  
    79.    try {  
    80.       
    81.      // 设置mapreduce.job.user.name为当前用户,之前的WordCount示例配置的为hdfs用户  
    82.      conf.set(MRJobConfig.USER_NAME,  
    83.          UserGroupInformation.getCurrentUser().getShortUserName());  
    84.        
    85.      // 设置hadoop.http.filter.initializers为AmFilterInitializer  
    86.      conf.set("hadoop.http.filter.initializers",   
    87.          "org.apache.hadoop.yarn.server.webproxy.amfilter.AmFilterInitializer");  
    88.        
    89.      // 设置mapreduce.job.dir为submitJobDir,比如/user/hdfs/.staging/job_1459913635503_0005  
    90.      conf.set(MRJobConfig.MAPREDUCE_JOB_DIR, submitJobDir.toString());  
    91.      LOG.debug("Configuring job " + jobId + " with " + submitJobDir   
    92.          + " as the submit dir");  
    93.        
    94.      // get delegation token for the dir  
    95.      // 获取路径的授权令牌:调用TokenCache的obtainTokensForNamenodes()静态方法  
    96.      TokenCache.obtainTokensForNamenodes(job.getCredentials(),  
    97.          new Path[] { submitJobDir }, conf);  
    98.        
    99.      // 获取密钥和令牌,并将它们存储到令牌缓存TokenCache中  
    100.      populateTokenCache(conf, job.getCredentials());  
    101.   
    102.      // generate a secret to authenticate shuffle transfers  
    103.      if (TokenCache.getShuffleSecretKey(job.getCredentials()) == null) {  
    104.        KeyGenerator keyGen;  
    105.        try {  
    106.           
    107.          int keyLen = CryptoUtils.isShuffleEncrypted(conf)   
    108.              ? conf.getInt(MRJobConfig.MR_ENCRYPTED_INTERMEDIATE_DATA_KEY_SIZE_BITS,   
    109.                  MRJobConfig.DEFAULT_MR_ENCRYPTED_INTERMEDIATE_DATA_KEY_SIZE_BITS)  
    110.              : SHUFFLE_KEY_LENGTH;  
    111.          keyGen = KeyGenerator.getInstance(SHUFFLE_KEYGEN_ALGORITHM);  
    112.          keyGen.init(keyLen);  
    113.        } catch (NoSuchAlgorithmException e) {  
    114.          throw new IOException("Error generating shuffle secret key", e);  
    115.        }  
    116.        SecretKey shuffleKey = keyGen.generateKey();  
    117.        TokenCache.setShuffleSecretKey(shuffleKey.getEncoded(),  
    118.            job.getCredentials());  
    119.      }  
    120.   
    121.      // 复制并且配置相关文件  
    122.      copyAndConfigureFiles(job, submitJobDir);  
    123.   
    124.      // 获取配置文件路径:job.xml  
    125.      Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir);  
    126.        
    127.      // Create the splits for the job  
    128.      LOG.debug("Creating splits at " + jtFs.makeQualified(submitJobDir));  
    129.        
    130.      // 调用writeSplits()方法,写分片数据文件job.split和分片元数据文件job.splitmetainfo,  
    131.      // 并获得计算得到的map任务数目maps  
    132.      int maps = writeSplits(job, submitJobDir);  
    133.        
    134.      // 配置信息中设置map任务数目mapreduce.job.maps为上面得到的maps  
    135.      conf.setInt(MRJobConfig.NUM_MAPS, maps);  
    136.        
    137.      LOG.info("number of splits:" + maps);  
    138.   
    139.      // write "queue admins of the queue to which job is being submitted"  
    140.      // to job file.  
    141.        
    142.      // 获取作业队列名queue,取参数mapreduce.job.queuename,参数未配置默认为default,  
    143.      // 之前的WordCount任务示例中,作业队列名queue就为default  
    144.      String queue = conf.get(MRJobConfig.QUEUE_NAME,  
    145.          JobConf.DEFAULT_QUEUE_NAME);  
    146.        
    147.      // 获取队列的访问权限控制列表AccessControlList实例acl,通过客户端通信协议ClientProtocol实例submitClient的getQueueAdmins()方法,传入队列名queue,  
    148.      // 实际上之前的WordCount任务示例中,这里获取的是*  
    149.      AccessControlList acl = submitClient.getQueueAdmins(queue);  
    150.        
    151.      // 配置信息中设置队列参数mapred.queue.default.acl-administer-jobs  
    152.      // 之前的WordCount任务示例中,该参数被设置成为*  
    153.      conf.set(toFullPropertyName(queue,  
    154.          QueueACL.ADMINISTER_JOBS.getAclName()), acl.getAclString());  
    155.   
    156.      // removing jobtoken referrals before copying the jobconf to HDFS  
    157.      // as the tasks don't need this setting, actually they may break  
    158.      // because of it if present as the referral will point to a  
    159.      // different job.  
    160.      // 清空缓存的令牌  
    161.      TokenCache.cleanUpTokenReferral(conf);  
    162.   
    163.      // 根据参数确定是否需要追踪令牌ID  
    164.      // 取参数mapreduce.job.token.tracking.ids.enabled,参数未配置默认为false  
    165.      if (conf.getBoolean(  
    166.          MRJobConfig.JOB_TOKEN_TRACKING_IDS_ENABLED,  
    167.          MRJobConfig.DEFAULT_JOB_TOKEN_TRACKING_IDS_ENABLED)) {  
    168.          
    169.     // 通过job获取令牌ID,并存储到trackingIds列表中  
    170.     // Add HDFS tracking ids  
    171.        ArrayList<String> trackingIds = new ArrayList<String>();  
    172.        for (Token<? extends TokenIdentifier> t :  
    173.            job.getCredentials().getAllTokens()) {  
    174.          trackingIds.add(t.decodeIdentifier().getTrackingId());  
    175.        }  
    176.          
    177.        // 将trackingIds列表中的内容设置到参数mapreduce.job.token.tracking.ids中  
    178.        conf.setStrings(MRJobConfig.JOB_TOKEN_TRACKING_IDS,  
    179.            trackingIds.toArray(new String[trackingIds.size()]));  
    180.      }  
    181.   
    182.      // Set reservation info if it exists  
    183.      // 如有必要,设置存在的预订信息  
    184.      // 参数为mapreduce.job.reservation.id  
    185.      ReservationId reservationId = job.getReservationId();  
    186.      if (reservationId != null) {  
    187.        conf.set(MRJobConfig.RESERVATION_ID, reservationId.toString());  
    188.      }  
    189.   
    190.      // Write job file to submit dir  
    191.      // 调用writeConf()方法,写入作业配置信息至文件job.xml  
    192.      writeConf(conf, submitJobFile);  
    193.        
    194.      //  
    195.      // Now, actually submit the job (using the submit name)  
    196.      // 调用printTokens()方法打印令牌信息到Log文件  
    197.      printTokens(jobId, job.getCredentials());  
    198.        
    199.      // 通过客户端通信协议ClientProtocol实例submitClient的submitJob()方法提交作业,  
    200.      // 并获取作业状态JobStatus实例status  
    201.      // 由集群连接一文的分析我们可以知道,这个submitClient实际上是YARNRunner或LocalJobRunner对象,  
    202.      // 最终调用的是二者的submitJob()方法,我们留待以后分析  
    203.      status = submitClient.submitJob(  
    204.          jobId, submitJobDir.toString(), job.getCredentials());  
    205.        
    206.      // 如果作业状态JobStatus实例status不为null,直接返回,否则抛出无法加载作业的IO异常  
    207.      if (status != null) {  
    208.        return status;  
    209.      } else {  
    210.        throw new IOException("Could not launch job");  
    211.      }  
    212.    } finally {  
    213.       
    214.      // 最终,抛出无法加载作业的IO异常前,调用文件系统FileSystem实例jtFs的delete()方法,  
    215.      // 删除作业提交的相关目录或文件submitJobDir  
    216.      if (status == null) {  
    217.        LOG.info("Cleaning up the staging area " + submitJobDir);  
    218.        if (jtFs != null && submitJobDir != null)  
    219.          jtFs.delete(submitJobDir, true);  
    220.   
    221.      }  
    222.    }  
    223.  }  

            submitJobInternal()方法篇幅比较长,逻辑也很复杂,本文先介绍下它的大体逻辑,后续分文会介绍各个环节的详细内容,且下面涉及到的之前WordCount作业示例在《Hadoop2.6.0版本MapReudce示例之WordCount(一)》及其姊妹篇中,敬请注意!submitJobInternal()方法大体逻辑如下:

            1、调用checkSpecs()方法,校验作业输出路径是否配置,且是否已存在:

                  正确的情况应该是已配置且未存在,输出路径配置参数为mapreduce.output.fileoutputformat.outputdir,之前WordCount作业的输出路径配置为hdfs://nameservice1/output/output;

            2、从作业job中获取配置信息conf;

            3、调用addMRFrameworkToDistributedCache()方法添加应用框架路径到分布式缓存中;

            4、通过JobSubmissionFiles的getStagingDir()静态方法获取作业执行时阶段区域路径jobStagingArea:

                  取参数yarn.app.mapreduce.am.staging-dir,参数未配置默认为/tmp/Hadoop-yarn/staging,然后后面是/提交作业用户名/.staging,通过之前的WordCount任务的执行,我们查看历史记录,得知参数yarn.app.mapreduce.am.staging-dir配置的为/user,而提交作业用户名为hdfs,所以完整的路径应该为/user/hdfs/.staging;

            5、获取当前本机地址ip;

            6、确定提交作业的主机地址、主机名,并设置入配置信息conf,对应参数分别为mapreduce.job.submithostname、mapreduce.job.submithostaddress;

            7、生成作业ID,即JobID实例jobId:

                  通过客户端通信协议ClientProtocol实例submitClient的getNewJobID()方法生成作业ID,即JobID实例jobId;

            8、 将jobId设置入job;

            9、构造提交作业路径Path实例submitJobDir:

                   jobStagingArea后接/jobId,比如/job_1459913635503_0005,之前WordCount作业的完整路径为/user/hdfs/.staging/job_1459913635503_0005;

            10、设置作业一些参数:

                     10.1、设置mapreduce.job.user.name为当前用户,之前的WordCount示例配置的为hdfs用户;

                     10.2、设置hadoop.http.filter.initializers为AmFilterInitializer;

                     10.3、设置mapreduce.job.dir为submitJobDir,比如/user/hdfs/.staging/job_1459913635503_0005;

            11、获取路径的授权令牌:调用TokenCache的obtainTokensForNamenodes()静态方法;

            12、通过populateTokenCache()方法获取密钥和令牌,并将它们存储到令牌缓存TokenCache中;

            14、复制并且配置相关文件:通过copyAndConfigureFiles()方法实现;

            15、获取配置文件路径:job.xml;

            16、调用writeSplits()方法,写分片数据文件job.split和分片元数据文件job.splitmetainfo,并获得计算得到的map任务数目maps;

            17、配置信息中设置map任务数目mapreduce.job.maps为上面得到的maps;

            18、获取作业队列名queue,取参数mapreduce.job.queuename,参数未配置默认为default,之前的WordCount任务示例中,作业队列名queue就为default;

            19、获取队列的访问权限控制列表AccessControlList实例acl:

                    通过客户端通信协议ClientProtocol实例submitClient的getQueueAdmins()方法,传入队列名queue,实际上之前的WordCount任务示例中,这里获取的是*;

            20、配置信息中设置队列参数mapred.queue.default.acl-administer-jobs,之前的WordCount任务示例中,该参数被设置成为*;

            21、清空缓存的令牌:通过TokenCache的cleanUpTokenReferral()方法实现;

            22、根据参数确定是否需要追踪令牌ID,如果需要的话:

                    取参数mapreduce.job.token.tracking.ids.enabled,参数未配置默认为false,通过job获取令牌ID,并存储到trackingIds列表中,将trackingIds列表中的内容设置到参数mapreduce.job.token.tracking.ids中;

            23、如有必要,设置存在的预订信息:参数为mapreduce.job.reservation.id;

            24、调用writeConf()方法,写入作业配置信息至文件job.xml;

            25、调用printTokens()方法打印令牌信息到Log文件;

            26、通过客户端通信协议ClientProtocol实例submitClient的submitJob()方法提交作业,并获取作业状态JobStatus实例status:

                    由集群连接一文的分析我们可以知道,这个submitClient实际上是YARNRunner或LocalJobRunner对象,最终调用的是二者的submitJob()方法,我们留待以后分析;

            27、如果作业状态JobStatus实例status不为null,直接返回,否则抛出无法加载作业的IO异常:

                    最终,抛出无法加载作业的IO异常前,调用文件系统FileSystem实例jtFs的delete()方法,删除作业提交的相关目录或文件submitJobDir。

            整体流程如上,对于关键步骤的主要细节,限于篇幅,敬请关注《MapReduce源码分析之JobSubmitter(二)》!

  • 相关阅读:
    项目游戏开发日记 No.0x000002
    项目游戏开发日记 No.0x000001
    项目游戏开发日记 No.0x00000
    需求获取常见的方法是进行客户访谈,结合你的实践谈谈会遇到什么问题,你是怎么解决的?
    当下大部分互联网创业公司为什么都愿意采用增量模型来做开发?
    迷茫踌躇的2018,整装待发的2019
    阿里云轻量应用服务器 搭建配置详解
    JS常用数组方法及实例
    使用CSS隐藏HTML元素的四种常用方法
    标签种类及CSS引入方法
  • 原文地址:https://www.cnblogs.com/jirimutu01/p/5556366.html
Copyright © 2020-2023  润新知