Day6.Hadoop学习笔记4

一、MapReduce任务提交(前4步)

(源码分析——前4步发生在client node 上)
Job.java 从1292行

  /**
   * Submit the job to the cluster and return immediately.
   * @throws IOException
   */
  public void submit() 
         throws IOException, InterruptedException, ClassNotFoundException {
    ensureState(JobState.DEFINE);
    setUseNewAPI();
    connect();
    //经过connect拿到cluster,然后.getFileSystem()拿HDFS配置,.getClient()拿YarnRunner配置
    final JobSubmitter submitter = getJobSubmitter(cluster.getFileSystem(), cluster.getClient());
    //不要进入doAs,匿名内部类形式,模板编程思想,重点看核心功能
      status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>() {
      public JobStatus run() throws IOException, InterruptedException, 
      ClassNotFoundException {
        return submitter.submitJobInternal(Job.this, cluster);	//核心,关注重点
      }
    });
    state = JobState.RUNNING;
    LOG.info("The url to track the job: " + getTrackingURL());
   }

JobSubmitter.java 从432行

  //在第158行,可以看到创建的文件夹信息。
  // configures -files, -libjars and -archives.
  private void copyAndConfigureFiles(Job job, Path submitJobDir,
      short replication) throws IOException {
    Configuration conf = job.getConfiguration();
    if (!(conf.getBoolean(Job.USED_GENERIC_PARSER, false))) {
      LOG.warn("Hadoop command-line option parsing not performed. " +
               "Implement the Tool interface and execute your application " +
               "with ToolRunner to remedy this.");
    }

    // get all the command line arguments passed in by the user conf
    String files = conf.get("tmpfiles");	
    String libjars = conf.get("tmpjars");	
    String archives = conf.get("tmparchives");
    String jobJar = job.getJar();	//启动jar(代码片段)

  /**
   * Internal method for submitting jobs to the system.
   * 
   * <p>The job submission process involves:
   * <ol>
   *   <li>
   *   Checking the input and output specifications of the job.
   *   </li>
   *   <li>
   *   Computing the {@link InputSplit}s for the job.
   *   </li>
   *   <li>
   *   Setup the requisite accounting information for the 
   *   {@link DistributedCache} of the job, if necessary.
   *   </li>
   *   <li>
   *   Copying the job's jar and configuration to the map-reduce system
   *   directory on the distributed file-system. 
   *   </li>
   *   <li>
   *   Submitting the job to the <code>JobTracker</code> and optionally
   *   monitoring it's status.
   *   </li>
   * </ol></p>
   * @param job the configuration to submit
   * @param cluster the handle to the Cluster
   * @throws ClassNotFoundException
   * @throws InterruptedException
   * @throws IOException
   */
  JobStatus submitJobInternal(Job job, Cluster cluster) 
  throws ClassNotFoundException, InterruptedException, IOException {

    //执行OutputFormat的checkOutputSprcs方法,就是判断目录是否存在 
    checkSpecs(job);	

    Configuration conf = job.getConfiguration();
    addMRFrameworkToDistributedCache(conf);

    Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);	//文件临时中转区 
    
    InetAddress ip = InetAddress.getLocalHost();
    if (ip != null) {
      submitHostAddress = ip.getHostAddress();
      submitHostName = ip.getHostName();
      conf.set(MRJobConfig.JOB_SUBMITHOST,submitHostName);
      conf.set(MRJobConfig.JOB_SUBMITHOSTADDR,submitHostAddress);
    }
    JobID jobId = submitClient.getNewJobID();	//拿到每个jobId
    job.setJobID(jobId);
    Path submitJobDir = new Path(jobStagingArea, jobId.toString());	//拼接成提交目录
    JobStatus status = null;
    try {
      conf.set(MRJobConfig.USER_NAME,
          UserGroupInformation.getCurrentUser().getShortUserName());
      conf.set("hadoop.http.filter.initializers", 
          "org.apache.hadoop.yarn.server.webproxy.amfilter.AmFilterInitializer");
      conf.set(MRJobConfig.MAPREDUCE_JOB_DIR, submitJobDir.toString());
      LOG.debug("Configuring job " + jobId + " with " + submitJobDir 
          + " as the submit dir");
      // get delegation token for the dir
      TokenCache.obtainTokensForNamenodes(job.getCredentials(),
          new Path[] { submitJobDir }, conf);
      
      populateTokenCache(conf, job.getCredentials());

      // generate a secret to authenticate shuffle transfers
      if (TokenCache.getShuffleSecretKey(job.getCredentials()) == null) {
        KeyGenerator keyGen;
        try {
         
          int keyLen = CryptoUtils.isShuffleEncrypted(conf) 
              ? conf.getInt(MRJobConfig.MR_ENCRYPTED_INTERMEDIATE_DATA_KEY_SIZE_BITS, 
                  MRJobConfig.DEFAULT_MR_ENCRYPTED_INTERMEDIATE_DATA_KEY_SIZE_BITS)
              : SHUFFLE_KEY_LENGTH;
          keyGen = KeyGenerator.getInstance(SHUFFLE_KEYGEN_ALGORITHM);
          keyGen.init(keyLen);
        } catch (NoSuchAlgorithmException e) {
          throw new IOException("Error generating shuffle secret key", e);
        }
        SecretKey shuffleKey = keyGen.generateKey();
        TokenCache.setShuffleSecretKey(shuffleKey.getEncoded(),
            job.getCredentials());
      }

      copyAndConfigureFiles(job, submitJobDir);
      //【☆】上传运行时所需要的jar和第三方依赖,调用conf.set("temjars","~jar包路径~")
      
      Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir);
      //调用的是,new Path(jobSubmitDir, "job.xml");
      //job.xml把所有集群配置信息汇总,包括之前配置的core-site.xml、hdfs-site.xml
      
      // Create the splits for the job
      LOG.debug("Creating splits at " + jtFs.makeQualified(submitJobDir));
      
      //【☆】在任务提交初期,需要进行切片计算
      int maps = writeSplits(job, submitJobDir);
        
      conf.setInt(MRJobConfig.NUM_MAPS, maps);
      LOG.info("number of splits:" + maps);

      // write "queue admins of the queue to which job is being submitted"
      // to job file.
      String queue = conf.get(MRJobConfig.QUEUE_NAME,
          JobConf.DEFAULT_QUEUE_NAME);
      AccessControlList acl = submitClient.getQueueAdmins(queue);
      conf.set(toFullPropertyName(queue,
          QueueACL.ADMINISTER_JOBS.getAclName()), acl.getAclString());

      // removing jobtoken referrals before copying the jobconf to HDFS
      // as the tasks don't need this setting, actually they may break
      // because of it if present as the referral will point to a
      // different job.
      TokenCache.cleanUpTokenReferral(conf);

      if (conf.getBoolean(
          MRJobConfig.JOB_TOKEN_TRACKING_IDS_ENABLED,
          MRJobConfig.DEFAULT_JOB_TOKEN_TRACKING_IDS_ENABLED)) {
        // Add HDFS tracking ids
        ArrayList<String> trackingIds = new ArrayList<String>();
        for (Token<? extends TokenIdentifier> t :
            job.getCredentials().getAllTokens()) {
          trackingIds.add(t.decodeIdentifier().getTrackingId());
        }
        conf.setStrings(MRJobConfig.JOB_TOKEN_TRACKING_IDS,
            trackingIds.toArray(new String[trackingIds.size()]));
      }

      // Set reservation info if it exists
      ReservationId reservationId = job.getReservationId();
      if (reservationId != null) {
        conf.set(MRJobConfig.RESERVATION_ID, reservationId.toString());
      }

      //【☆】上下文配置,生成job.xml
      writeConf(conf, submitJobFile);
      
      //
      // Now, actually submit the job (using the submit name)
      //
      printTokens(jobId, job.getCredentials());
      //提交.submitJob方法————>之后的步骤,去到另一端yarn中,在客户端不再影响
      status = submitClient.submitJob(
          jobId, submitJobDir.toString(), job.getCredentials());
      if (status != null) {
        return status;
      } else {
        throw new IOException("Could not launch job");
      }
    } finally {
      if (status == null) {
        LOG.info("Cleaning up the staging area " + submitJobDir);
        if (jtFs != null && submitJobDir != null)
          jtFs.delete(submitJobDir, true);

      }
    }
  }

图1

二、如何解决Jar包依赖问题

  • 计算节点依赖

方案1 多测试用,跨平台远程调用,设置tmpjars

conf.set("tmpjars","file://jar1路径,file://jar2路径");

方案2 必须注意书写格式,详尽的run没有写在main函数中。

public class CustomJobSubmitter extends Configured implements Tool {
    //封装job
    public int run(String[] args) throws Exception {
        Configuration conf=getConf();
        //封装job ...
       return 0;
    }
     public static void main(String[] args) throws Exception {
        ToolRunner.run(new CustomJobSubmitter(),args);
    }
}
配合:
hadoop jar xxx.jar 入口类 -libjars 依赖jar1路径,依赖jar1路径
  • 提交节点依赖(ClientNode依赖)

思路,将提交依赖的jar包添加到hadoop的类路径下;classpath

[root@CentOS ~]# hadoop classpath
/usr/hadoop-2.6.0/etc/hadoop:/usr/hadoop-2.6.0/share/hadoop/common/lib/*:/usr/hadoop-2.6.0/share/hadoop/common/*:/usr/hadoop-2.6.0/share/hadoop/hdfs:/usr/hadoop-2.6.0/share/hadoop/hdfs/lib/*:/usr/hadoop-2.6.0/share/hadoop/hdfs/*:/usr/hadoop-2.6.0/share/hadoop/yarn/lib/*:/usr/hadoop-2.6.0/share/hadoop/yarn/*:/usr/hadoop-2.6.0/share/hadoop/mapreduce/lib/*:/usr/hadoop-2.6.0/share/hadoop/mapreduce/*:/usr/hadoop-2.6.0/contrib/capacity-scheduler/*.jar

一般解决办法是将通过配置HADOOP_CLASSPATH环境变量的方式解决:

[root@CentOS ~]# vi .bashrc 

HADOOP_CLASSPATH=/root/mysql-connector-java-5.1.38.jar
HADOOP_HOME=/usr/hadoop-2.6.0
JAVA_HOME=/usr/java/latest
PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
CLASSPATH=.
export JAVA_HOME
export PATH
export CLASSPATH
export HADOOP_HOME
export HADOOP_CLASSPATH
[root@CentOS ~]# source .bashrc 
[root@CentOS ~]# hadoop classpath
/usr/hadoop-2.6.0/etc/hadoop:/usr/hadoop-2.6.0/share/hadoop/common/lib/*:/usr/hadoop-2.6.0/share/hadoop/common/*:/usr/hadoop-2.6.0/share/hadoop/hdfs:/usr/hadoop-2.6.0/share/hadoop/hdfs/lib/*:/usr/hadoop-2.6.0/share/hadoop/hdfs/*:/usr/hadoop-2.6.0/share/hadoop/yarn/lib/*:/usr/hadoop-2.6.0/share/hadoop/yarn/*:/usr/hadoop-2.6.0/share/hadoop/mapreduce/lib/*:/usr/hadoop-2.6.0/share/hadoop/mapreduce/*:`/root/mysql-connector-java-5.1.38.jar`:/usr/hadoop-2.6.0/contrib/capacity-scheduler/*.jar

三、Shuffle浅析【重难点】

在MapTask类中,有一个方法runNewMapper,他里边有这样一段代码:

	// get an output object
    if (job.getNumReduceTasks() == 0) {
    	//是通过OutputFormat#getRecordWriter将数据直接写入目的地
      output = new NewDirectOutputCollector(taskContext, job, umbilical, reporter);
    } else {
    	//Map Reduce 统计  数据写入磁盘 -> Map Shuffle
      output = new NewOutputCollector(taskContext, job, umbilical, reporter);
    }
  1. Task任务可以只有map,而没有reduce
  • 场景示例:通过 点击商品、点关注、加购物车,来计算物品推荐度;对应不同操作,有不同的权重打分,后台采集用户行为数据(日志);将数据转化成一定的分组情况。当然再复杂的日志,也可以使用正则来抽取。
    这就是数据的"ETL",数据的"清洗"。
  1. 设置job.setNumReduceTasks(0);意义?【面试题】
    不设置的话,默认为1;设置0起到短路特性,必执行第一个分支。
output = new NewDirectOutputCollector(taskContext, job, umbilical, reporter);	
//实际上,调用的是OutputFormat的write方法

答:短路shuffle过程,直接将Map端的结果作为输出。
有以下使用场景:
数据查询(海量数据)
做数据的"清洗"、“降噪”(数据有价)

小tip:“抖动降噪”,利用定时任务来写日志,进行数据的有效过滤。

  • 数据清洗
    如:系统推荐
    采集日志数据(点击、浏览、购买行为)–> HDFS --> 数据降噪处理 (有价值数据)–>数据格式一致化转换(符合算法需要大数据格式userid,itemid,score)–> 算法分析Mahout–> 推荐数据(userid itemid recommand)–>业务干预(清洗、过滤)–> RDBMS --> 可视化,推荐
  1. Shuffle我们把 k-v 转成 k-v[ ] 数组,的过程,我们称之为Shuffle.
 output = new NewOutputCollector(taskContext, job, umbilical, reporter);
 //当我们走下边的分支时,进入shuffle过程。
  • MapOutputBuffer 一个circular分区的环状的缓冲区。

图shuffle

① 当我们map不停往外写数据时,为了减少磁盘io的频度,提升写出的性能,会为每个mapTask准备环状的缓冲区。这个缓冲区的大小默认100MB,环状缓冲区可以边写边flush,同时它有一个阈值80%,即达到80%再进行flush。 flush时依然可以写入。
这种机制不会覆盖数据,但当flush没有写入快时,会产生阻塞。
flush在缓冲区构建时,内部启动线程SpillThread(溢写线程),由它来监控当前缓冲区的状态。

②分区的作用?partition,负载均衡。reduce的本质是统计,统计的依据是拿到key的所有值。对应的一个key的值,应该都在一个分区中,即保证key一样的记录落在同一个分区中。所以在进入缓冲区前,已经将分区分配好了。

 	@Override	//MapTask.java 711行
    public void write(K key, V value) throws IOException, InterruptedException {
        //写方法,调用collect方法 三个参数 key、value、int类型的区号
      collector.collect(key, value, partitioner.getPartition(key, value, partitions));
    }

最终会经过计算,拿到区号

 	partitions = jobContext.getNumReduceTasks();	//总分区数 == reduce数 	 696行
      if (partitions > 1) {
        partitioner = (org.apache.hadoop.mapreduce.Partitioner<K,V>)
          ReflectionUtils.newInstance(jobContext.getPartitionerClass(), job);
      } else {
          //进入这里一定是1 返回区号0 
        partitioner = new org.apache.hadoop.mapreduce.Partitioner<K,V>() {
          @Override
          public int getPartition(K key, V value, int numPartitions) {
            return partitions - 1;
          }
        };
      }

②为什么排序?sort在溢写之前完成,SortAndSpile
③一次mapTask会产生n次溢写,至少一次,每次溢写产生SpillFile(溢写文件),会产生n个溢写文件。有几个maptask就会有几个溢写文件。
④每一个reduceTask下载自己分区的数据,拿到对应分区排过序的数据,但分区之间没有排序,还需要排序,并合并称为 key-value[ ] 数组

⑤综上,我们把这两个部分称为Shuffle。
Map端:溢写(分区,排序) ,溢写合并
Reduce端:下载,归并,排序

  1. 什么是溢写?【面试题】
    答:缓冲区什么样子?溢写的时机? 结合上方的 ① 回答。
    MapOutputBuffer大小 mapreduce.task.io.sort.mb 100MB mapreduce.map.sort.spill.percent 0.8 --> flush磁盘的过程(分区、SpillThread、sort、merge)

  2. 如何自定义Map端key,value类型(如何自定义Writable或者WritableComparable接口)

  • 如果是自定义Map端输出的KEY类型,必须满足可排序可序列化,因此用户需要实现WritableComparable
  • 如果自定义Map端的Value类型,只需要满足可序列化,实现Writable

注意如何自定义Reduce端输出key、value类型取决于OutputFormat中的RecordWriter对Key/Value的要求。如果用户想干预MR的排序,可以自定义Map端Key类型,覆盖compareTo方法。

  1. 如何干预分区策略?
    通过job.setPartitionerClass(XxxPartitioner.class);设置分区策略。
public class XxxPartitioner<K2, V2> implements Partitioner<K2, V2> {
  public int getPartition(K2 key, V2 value, int numReduceTasks) {
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
  }
}

注意默认MR框架做的都是分区内部排序,如果想全局排序,需要做分区排序,这有可能导致分区数据不均匀,导致计算倾斜。

  1. Hadoop的MapReduce如何实现全局排序?
    保全分区策略有序,结果自然是全局有序的缺点:区间设置不合理,到时数据的倾斜。hadoop提供一个TotalOrderPartitioner 实现全局排序

  2. 数据倾斜【面试题:什么是MapReduce计算过程中的数据倾斜问题?】
    实现分区策略?(错区)
    分区不均匀,不会影响map阶段,但会影响reduce阶段。
    必须保证key相同的记录,落在同一个分区,因此无论如何实现Partitioner都无法解决数据倾斜。需要合理的定制用户的Key,使得分区数据相对均匀。

因为不合理的KEY,导致了数据的分布不均匀。选择合适的key作为统计依据,使得数据能够在爱分区均匀分布。一般需要程序员对分析的数据有一定的预判!

  1. 如何优化MR程序?(Map端Gzip压缩(通用的))
  • 对Map端输出结果进行压缩,减少Reduce shuffle数据下载量(通用),可以有效,减少Reduce Shuffle过程的网络带宽占用。可以在计算过程中需要消耗额外的CPU进行数据的压缩和解压缩。
conf.setBoolean("mapreduce.map.output.compress", true);
conf.setClass("mapreduce.map.output.compress.codec",GzipCodec.class,
              CompressionCodec.class);

只能在实际的环境下测试,本地仿真测试会失败.

  • 如果计算具备可以迭代特性,可以考虑在Map端执行局部的Reduce,我们将该方法称为Combiner
  1. MR另一种优化方案——Combiner
    在Map端发生溢写的时候或者是合并溢写文件的时候,对Map端的输出结果执行Reduce逻辑,但是这种执行后的,结果格式必须不改变原有Map端输出的格式类型,并且该预处理的结果不影响ReduceShuffle以及Reducer逻辑。
job.setCombinerClass(XXXReducer.class)
  • CombinerReducer实际就是一个Class extends Reducer,combiner一般发生在溢写阶段和溢写文件合并阶段。
  • Combiner输入k-v和输出k-v类型必须一致
  • v的值,必须具备可迭代计算特点
  • 即1.计算逻辑不影响(经验积累)、2.类型兼容
减少溢写文件大小,减少后续key的排序数量

其他、

  • ETL(Extract-Transform-Load)
    ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析依据。 ETL是BI项目重要的一个环节。 通常情况下,在BI项目中ETL会花掉整个项目至少1/3的时间,ETL设计的好坏直接关接到BI项目的成败。
    ETL的设计分三部分:数据抽取、数据的清洗转换、数据的加载。在设计ETL的时候我们也是从这三部分出发。数据的抽取是从各个不同的数据源抽取到ODS(Operational Data Store,操作型数据存储)中——这个过程也可以做一些数据的清洗和转换),在抽取的过程中需要挑选不同的抽取方法,尽可能的提高ETL的运行效率。ETL三个部分中,花费时间最长的是“T”(Transform,清洗、转换)的部分,一般情况下这部分工作量是整个ETL的2/3。数据的加载一般在数据清洗完了之后直接写入DW(Data Warehousing,数据仓库)中去。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值