大数据笔试真题集锦---第四章:Hadoop面试题

第四章目录

第四章 Hadoop

HDFS是一个分布式高可靠的文件存储系统

Yarn是一个分布式的hadoop资源调度器

4.1 原理

4.1.1 HDFS 读写流程

4.1.1.1 写数据流程

  1. 客户端发出请求 hdfs dfs -put /etc/profile /qf/data
  2. namenode查看维护的目录结构,检查/qf/data是否存在,如不存在直接报错”no such file or directory“,如存在返回给客户端同意上传文件请求,将操作写入日志文件
  3. 客户端请求上传第一个块,询问namenode块的存储位置
  4. namenode查看自己的datanode池,返回给客户端一个datanode列表
  5. 客户端发出请求建立pipeline
  6. 客户端先把文件写入缓存,达到一个块的大小时,会在客户端和第一个datanode建立连接开始流式的传输数据,这个datanode会一小部分一小部分(4K)的接收数据然后写入本地仓库,同时会把这些数据传输到第二个datanode,第二个datanode也同样一小部分一小部分的接收数据并写入本地仓库,同时传输给第三个datanode...(在流式复制时,逐级传输和响应采用响应队列来等待传输结果。队列响应完成后返回给客户端)
  7. 第一个数据块传输完成后会使用同样的方式传输下面的数据块直到整个文件上传完成。
  8. 整个文件完成,namenode更新内存元数据

4.1.1.2 读数据流程

  1. 客户端向namenode发起RPC调用,请求读取文件数据。
  2. namenode检查文件是否存在,如果存在则获取文件的元信息(blockid以及对应的datanode列表)。
  3. 客户端收到元信息后选取一个网络距离最近的datanode,依次请求读取每个数据块。客户端首先要校检文件是否损坏,如果损坏,客户端会选取另外的datanode请求。
  4. datanode与客户端建立socket连接,传输对应的数据块,客户端收到数据缓存到本地,之后写入文件。
  5. 依次传输剩下的数据块,直到整个文件合并完成。

4.1.2 MapReduce的原理

4.1.2.1 MapperTask

使用逻辑切片的方式划分block,调用InputFormat中的算法进行划分,TextInputFormat默认按照128M进行划分(默认一个block至少有一片),每个分片以行首开头,以行尾结尾。

每个分片对应一个mapper读入数据后,调用mapper函数转换成(k,v)形式,然后按照分区排序,分批溢写到磁盘。

4.1.2.2 Shuffle

从map分区排序溢写到reduce拉取数据的过程称为shuffle。

每个mapTask都有一个缓存区域,当调用write方法时,数据先写入到缓存区域中,数据写满80%后会先进行分组排序然后溢写到磁盘,剩下的20%继续同步写入数据。

如果定义了combine方法,则这里会发生分组内的聚合。

一个mapTask最终会生成多个临时文件,最后将这些临时文件使用归并排序合并成一个大文件,并按照分区器规则维护一个索引文件,在合并过程中也可以发生分组内聚合。

4.1.2.3 reducerTask

数量由用户设定,每个reducer对应一个分区数据。

reducer拉取分区数据到本地,一次性读入一组数据执行reduce方法,然后按照OutPutFormat的输出格式将最终结果输出到hdfs,每个reducer对应一个文件。

4.1.3 Yarn Job 提交流程

4.1.4 Hadoop三种任务调度

4.1.4.1 FIFO Scheduler

将所有的Applications放到队列中,先按照作业的优先级高低、再按照到达时间的先后,为每个app分配资源。如果第一个app需要的资源被满足了,如果还剩下了资源并且满足第二个app需要的资源,那么就为第二个app分配资源,and so on。 
优点:简单,不需要配置。 
缺点:不适合共享集群。如果有大的app需要很多资源,那么其他app可能会一直等待。 

4.1.4.2 Capacity Scheduler
CapacityScheduler用于一个集群(集群被多个组织共享)中运行多个Application的情况,目标是最大化吞吐量和集群利用率。

CapacityScheduler允许将整个集群的资源分成多个部分,每个组织使用其中的一部分,即每个组织有一个专门的队列,每个组织的队列还可以进一步划分成层次结构(**Hierarchical Queues**),从而允许组织内部的不同用户组的使用。

每个队列内部,按照FIFO的方式调度Applications。当某个队列的资源空闲时,可以将它的剩余资源共享给其他队列。

4.1.4.3 Fair Scheduler 
FairScheduler允许应用在一个集群中公平地共享资源。默认情况下FairScheduler的公平调度只基于内存,也可以配置成基于memory and CPU。当集群中只有一个app时,它独占集群资源。当有新的app提交时,空闲的资源被新的app使用,这样最终每个app就会得到大约相同的资源。可以为不同的app设置优先级,决定每个app占用的资源百分比。FairScheduler可以让短的作业在合理的时间内完成,而不必一直等待长作业的完成。

4.1.5 环形缓冲区(源码级)

  1. 环形缓冲区其实是一个字节数组,在MapTask.MapOutputBuffer中定义的。数组中存放着key、value的序列化数据和key、value的元数据信息
  2. key/value的元数据存储的格式是int类型,每个key/value对应一个元数据,元数据由4个int组成,第一个int存放value的起始位置,第二个存放key的起始位置,第三个存放partition,最后一个存放value的长度。
  3. key/value序列化的数据和元数据在环形缓冲区中的存储是由equator分隔的,key/value按照索引递增的方向存储,meta则按照索引递减的方向存储,将其数组抽象为一个环形结构之后,以equator为界,key/value顺时针存储,meta逆时针存储。
  4. 环形缓冲区:参考下图
  5. 我们可以将之抽象成环形状态
  6. 当数据量达到阈值(数组的80%)开始进行溢写,溢写后,释放空间,重置equator点。重置时,如果已经存在数据(20%的空间会继续写进来数据),一般不会移动kv对,而是移动元数据。

4.1.6 Hadoop 高可用原理

使用Active NameNode,StandbyNameNode两个节点解决单点问题。

两个结点通过JounalNode共享状态(主备一致)

通过ZKFC 选举Active实现自动切换(防止脑裂)

DataNode会同时向ActiveNN和StandbyNN发送心跳。

4.1.6.1 主备NameNode一致性原理

hadoop采用QJM共享存储机制来确保主备NameNode的元数据尽量一致。

  1. 启动一组奇数的JournalNode节点,主NameNode将操作日志并行写入本地和所有JournalNode节点,当超过一半JournalNode节点写入成功时判定为成功,遵循Paxos协议。
  2. 备NN会定时检查JournalNode上的EditLog,将它拉回本地
  3. 备NN会定期将本地的FS镜像文件和EditLog合并成新的镜像文件(保存最新的内存状态),传回主NN
  4. 主NN更新本地的镜像文件,删除旧的日志
  5. 发生主备切换时,选取最好的JN同步最新日志到本地(日志恢复机制),更新到最近状态

4.1.6.2 Hadoop如何防止脑裂

hadoop采用日志隔离双写和ZKFC两个机制预防脑裂。

  1. JournalNode集群会为主节点设置一个Epoch并分发到各个JN节点,当发生主备切换时,会将Epoch+1赋予新的主节点和各个JN节点
  2. JN在接收日志前会检查主节点Epoch是否小于自己,如果小于则拒绝写入。
  3. 每个NN启动一个ZKFC进程,利用ZK的选举完成主备选举,恢复连接的主节点会被通知切换为备节点

4.1.6.3 NameNode故障数据恢复

首先进入安全模式: hdfs dfsadmin -safemode enter 然后刷一下active节点的log到image hdfs dfsadmin -saveNamespace 然后将active节点的image文件全部拷贝到故障节点的相应目录下 然后重启故障namenode 最后hdfs namenode -bootstrapStandby 到此,故障解决。 后来还解决过一次hdfs的block丢失的问题,也是将原先的image全部拷贝回来搞定的。 所以说,即便有ha,定期备份image文件还是很重要的。

4.2 其他

4.2.1 mapreduce 二次排序

待排序的数据具有多个字段,首先对第一个字段进行排序,第一个字段相同的情况下,再按照第二个字段进行排序,第二次排序不会破坏第一次排序的结果。这个过程称之为二次排序。

原始数据如下:

输出结果数据如下:

第一种方式:简答粗暴

第一个字段在规约到reduce端的reduce函数之前排好序。而我们只需要在进入reduce函数后,对第二个字段进行再次排序即可。如下代码:

 @Override
 public void reduce(Text key, Iterable<IntWritable> values, Context context)
      throws IOException, InterruptedException {
      List<Integer> valuesList = new ArrayList<Integer>();

      // 取出value
      for(IntWritable value : values) {
          valuesList.add(value.get());
      }
      // 进行排序
      Collections.sort(valuesList);
      for(Integer value : valuesList) {
         context.write(key, new IntWritable(value));
      }
 }

但是,如果把排序工作都放到reduce端完成,当values序列长度非常大时,回对cpu和内存造成极大的负载。

第二种方式:

将map端输出的<key,value>中的key和value组合成一个新的key(称为newKey),value值不变。这里就变成<(key,value),value>,在针对newKey排序的时候,如果key相同,就再对value进行排序。 需要自定义的地方

  1. 自定义数据类型实现组合key 实现方式:继承WritableComparable
  2. 自定义partioner,形成newKey后保持分区规则仍然按照key进行。保证不打乱原来的分区。 实现方式:继承partitioner
  3. 自定义分组,保持分组规则仍然按照key进行。不打乱原来的分组实现方式:继承RawComparator

4.2.2 什么是MapReduce

  1. MapReduce是Hadoop的一个核心技术、是一个基于分布式的对大数据集进行并行处理的一个计算框架。
  2. 核心思想是移动计算而非数据。
  3. 整个计算流程分为两个阶段,一个是map阶段,一个是reduce阶段
  • map阶段 一个mapreduce作业在map阶段会先对数据进行逻辑分片处理,一个逻辑分片对应一个MapTask。每一个MapTask在处理数据的时候,都会将每一行数据解析成键值对<k1,v1>的形式,作为输入。输入给map函数。map函数处理后,也会以键值对<k2,v2>的形式作为输出。之后会对数据进行分区,排序等处理
  • reduce阶段 在进入reduce阶段之前。reduceTask会将自己要处理的分区数据fetch到reduceTask所在的机器节点上,然后以键值对<k2,list<v2>>作为reduce函数的输入,经过函数处理后,再以键值对<k3,v3>的形式作为输出。最终结果可以保存到hdfs系统上。

4.2.3 getSplit怎么分片的,分片的大小

4.2.3.1 split是逻辑分片,再mapTask任务开始前,将文件按照指定的大小进行逻辑切分。每一个部分称之为一个split。默认情况下,split的大小与block的大小相等。均为128M.

4.2.3.2 可以参考FileInputForamt类的getSplits()源码

    1. 会先获取三个参数的值,minSize,maxSize,blockSize
    2. 然后创建一个分片集合用于存储分片数据
    3. 获取文件的所有块信息,进行遍历
    4. 得到一个块的状态信息,然后判断是否可以切分
    5. 然后根据三个参数获取分片大小
    6. 循环判断文件剩余部分是否大于切片大小的1.1倍,
      a:大于的话就调用makeSplit方法创建当前块的逻辑分片

b:不大于的话,就将文件剩余的部分创建一个唯一的最后一个分片。

7. 将每一个逻辑分片添加到分片集合中,等待被使用

4.2.3.3 分片的大小由minSize,maxSize,blockSize三个参数决定


算法如下:
Math.max(minSize,Math.min(maxSize, blockSize)),其中maxSize是取得longValueMax的值
1.如果blockSize小于maxSize && blockSize 大于 minSize之间,那么split就是blockSize;
2.如果blockSize小于maxSize && blockSize 小于 minSize之间,那么split就是minSize;
3.如果blockSize大于maxSize && maxSize 大于 minSize之间,那么split就是maxSize;
4.如果blockSize大于maxSize && maxSize 小于 minSize之间,那么split就是maxSize(不存在这种关系)。

4.2.4 快排、归并怎么实现的?时间复杂度?

4.2.4.1 快速排序

    1. 是一个优秀的排序算法,O(n²)和Ω(nlgn),期望运行时间:θ(nlgn)且常数因子较小。
    2. 快速排序采用了分治的思想
  • 分:将数组划分成两个部分(核心,partition)
  • 治:递归的对划分的两个子数组进行排序

4.2.4.2 归并排序

    1. 归并排序(英语:Merge sort,或mergesort),是创建在归并操作上的一种有效的排序算法,效率为O(n log n)。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。
    2. 让左右两部分的元素先有序,然后把两个有序的部分合并为一个有序的过程。那么如何让左边的部分和右边的部分有序呢?继续把左边的部分分为两部分,然后排序。然后再把右边的部分分为两部分,再排序。这是一个递归的过程

代码如下图

4.2.5 基于yarn的任务运行时报错,用Linux命令行查看错误日志信息

1 查看某个job的日志,例如:yarn logs -applicationId application_1529513682598_0009
2 查看某个job的状态,例如:yarn application -status application_1529513682598_0009
3 终止某个job 注意:一般不要直接在UI界面或者是终端kill掉任务,该任务可能还会继续执行下去。

4 正确操作方法:停止job的执行命令如下:yarn application -kill application_1515118561637_0439

4.2.6 数据存储在hdfs格式,使用的什么压缩方式,压缩比多少

目前在Hadoop中用得比较多的有lzo,gzip,snappy,bzip2这4种压缩格式,笔者根据实践经验介绍一下这4种压缩格式的优缺点和应用场景,以便大家在实践中根据实际情况选择不同的压缩格式。

  1. gzip压缩
  • 优点:
    • 压缩率比较高,而且压缩/解压速度也比较快;
    • hadoop本身支持,在应用中处理gzip格式的文件就和直接处理文本一样;
    • 有hadoop native库;
    • 大部分linux系统都自带gzip命令,使用方便。
  • 缺点:不支持split。

应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用gzip压缩格式。譬如说一天或者一个小时的日志压缩成一个gzip文件,运行mapreduce程序的时候通过多个gzip文件达到并发。hive程序,streaming程序,和java写的mapreduce程序完全和文本处理一样,压缩之后原来的程序不需要做任何修改。

2. lzo压缩

  • 优点:
    • 压缩/解压速度也比较快,合理的压缩率;
    • 支持split,是hadoop中最流行的压缩格式;
    • 支持hadoop native库;
    • 可以在linux系统下安装lzop命令,使用方便。
  • 缺点:
    • 压缩率比gzip要低一些;
    • hadoop本身不支持,需要安装;
    • 在应用中对lzo格式的文件需要做一些特殊处理(为了支持split需要建索引,还需要指定inputformat为lzo格式)。

应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,lzo优点越明显。

3. snappy压缩

  • 优点:
    • 高速压缩速度和合理的压缩率;
    • 支持hadoop native库。
  • 缺点:
    • 不支持split;
    • 压缩率比gzip要低;
    • hadoop本身不支持,需要安装;
    • linux系统下没有对应的命令。
  • 应用场景:当mapreduce作业的map输出的数据比较大的时候,作为map到reduce的中间数据的压缩格式;或者作为一个mapreduce作业的输出和另外一个mapreduce作业的输入。

4. bzip2压缩

  • 优点:
    • 支持split;
    • 具有很高的压缩率,比gzip压缩率都高;
    • hadoop本身支持,但不支持native;
    • 在linux系统下自带bzip2命令,使用方便。
  • 缺点:
    • 压缩/解压速度慢;
    • 不支持native。
  • 应用场景:适合对速度要求不高,但需要较高的压缩率的时候,可以作为mapreduce作业的输出格式;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持split,而且兼容之前的应用程序(即应用程序不需要修改)的情况。

4.2.7 高可用的集群中namenode宕机了,怎么恢复的,数据如何转移

首先进入安全模式: 
hdfs dfsadmin -safemode enter 
然后刷一下active节点的log到image 
hdfs dfsadmin -saveNamespace 
然后将active节点的image文件全部拷贝到故障节点的相应目录下 
然后重启故障namenode 
最后hdfs namenode -bootstrapStandby 
到此,故障解决。 
后来还解决过一次hdfs的block丢失的问题,也是将原先的image全部拷贝回来搞定的。 
所以说,即便有ha,定期备份image文件还是很重要的。

4.2.8 hdfs的瓶颈

  1. fsimage加载阶段,主要耗时的操作:1.1)FSDirectory.addToParent(),功能是根据路径path生成节点INode,并加入目录树中,占加载时间的73%; 1.2)FSImage.readString和PermissionStatus.read操作是从fsimage的文件流中读取数据(分别是读取String和short)的操作,分别占加载时间的15%和8%;优化方案:对fsimage的持久化操作采用多线程技术,为目录列表中的每个目录存储开辟一个线程,用来存储fsimage文件。主线程等待所有存储的子线程完毕后完成对fsimage加载。这样,存储时间将取决于存储最慢的那个线程,达到了提高fsimage加载速度的目的,从而在一定程度上提升了NameNode启动速度。
  2. blockReport阶段,DataNode在启动时通过RPC调用NameNode.blockReport()方法, 主要耗时操作:2.1)FSNamesystem.addStoredBlock调用,对每一个汇报上来的block,将其于汇报上来的datanode的对应关系初始化到namenode内存中的BlocksMap表中,占用时间比为77%;此方法中主要耗时的两个阶段分别是FSNamesystem.countNode和DatanodeDescriptor.addBlock,后者是java中的插表操作,而FSNamesystem.countNode调用的目的是为了统计在BlocksMap中,每一个block对应的各副本中,有几个是live状态,几个是decommission状态,几个是Corrupt状态。 2.2)DatanodeDescriptor.reportDiff,主要是为了将该datanode汇报上来的blocks跟namenode内存中的BlocksMap中进行对比,以决定那个哪些是需要添加到BlocksMap中的block,哪些是需要添加到toRemove队列中的block,以及哪些是添加到toValidate队列中的block,占用时间比为20%;
  3. 锁的竞争成为性能瓶颈优化方案:其中锁内耗时的操作有FSEditLog.logXXX方法,可以考虑将FSEditLog的logXXX操作放到写锁外记录,但会引起记录的顺序不一致,可以通过在写锁内生成SerialNumber,在锁外按顺序输出来解决;
  4. UTF8/Unicode转码优化成为性能瓶颈优化方案:用SIMD指令的JVM Intrinsic转码实现,32bytes比目前Hadoop内pure Java实现快4倍,64bytes快十几倍。
  5. RPC框架中,N个Reader将封装的Call对象放入一个BlockingQueue,N个Handler从一个BlockingQueue中提取Call对象,使得此BlockingQueue成为性能瓶颈优化方案:采用多队列,一个Handler一个BlockingQueue,按callid分配队列。6.sendHeartbeat阶段,在DataNode调用namenode.sendHeartbeat()方法时会DF和DU两个类,它们通过Shell类的runComamnd方法来执行系统命令,以获取当前目录的 df, du 值,在runComamnd方法中实质是通过java.lang.ProcessBuilder类来执行系统命令的。该类由JVM通过Linux 内核来fork 子进程,子进程当然会完全继承父进程的所有内存句柄,jstack看到JVM此时线程状态大部分处于WAITING, 这个过程会影响DFSClient写入超时,或关闭流出错。

4.2.9 hdfs存放文件量过大怎么优化

  1. 那么可以通过调用 HDFS 的 sync() 方法(和 append 方法结合使用),每隔一定时间生成一个大文件。或者,可以通过写一个 MapReduce 程序来来合并这些小文件。
  2. HAR File Hadoop Archives (HAR files)是在 0.18.0 版本中引入到 HDFS 中的,它的出现就是为了缓解大量小文件消耗 NameNode 内存的问题。HAR 文件是通过在 HDFS 上构建一个分层文件系统来工作。HAR 文件通过 hadoop archive 命令来创建,而这个命令实际上是运行 MapReduce 作业来将小文件打包成少量的 HDFS 文件(译者注:将小文件进行合并成几个大文件)。对于客户端来说,使用 HAR 文件系统没有任何的变化:所有原始文件都可见以及可以访问(只是使用 har://URL,而不是 hdfs://URL),但是在 HDFS 中中文件个数却减少了。
    读取 HAR 文件不如读取 HDFS 文件更有效,并且实际上可能更慢,因为每个 HAR 文件访问需要读取两个索引文件以及还要读取数据文件本。
    尽管 HAR 文件可以用作 MapReduce 的输入,但是 Map 没有办法直接对共同驻留在 HDFS 块上的 HAR 所有文件操作。可以考虑通过创建一种 input format,充分利用 HAR 文件的局部性优势,但是目前还没有这种input format。需要注意的是:MultiFileInputSplit,即使在 HADOOP-4565 进行了改进,选择节点本地分割中的文件,但始终还是需要每个小文件的搜索。在目前看来,HAR 可能最好仅用于存储文档。
  3. SequenceFile 通常解决”小文件问题”的回应是:使用 SequenceFile。这种方法的思路是,使用文件名作为 key,文件内容作为 value
    在实践中这种方式非常有效。我们回到10,000个100KB大小的小文件问题上,你可以编写一个程序将合并为一个 SequenceFile,然后你可以以流式方式处理(直接处理或使用 MapReduce) SequenceFile。这样会带来两个优势:
    SequenceFiles 是可拆分的,因此 MapReduce 可以将它们分成块,分别对每个块进行操作;与 HAR 不同,它们支持压缩。在大多数情况下,块压缩是最好的选择,因为它直接对几个记录组成的块进行压缩,而不是对每一个记录进行压缩。将现有数据转换为 SequenceFile 可能会很慢。但是,完全可以并行创建一个一个的 SequenceFile 文件。Stuart Sierra 写了一篇关于将 tar 文件转换为 SequenceFile 的文章,像这样的工具是非常有用的,我们应该多看看。向前看,最好设计好数据管道,如果可能的话,将源数据直接写入 SequenceFile,而不是作为中间步骤写入小文件。
    与 HAR 文件不同,没有办法列出 SequenceFile 中的所有键,所以不能读取整个文件。Map File,类似于对键进行排序的 SequenceFile,维护部分索引,所以他们也不能列出所有的键

4.2.10 container的生命周期是什么,是整个job运行完成,还是说container上的任务完成后

  1. Container启动过程主要经历三个阶段:资源本地化、启动并运行container、资源回收,其中,资源本地化指创建container工作目录,从HDFS下载运行container所需的各种资源(jar包、可执行文件等)等,而资源回收则是资源本地化的逆过程,它负责清理各种资源,它们均由ResourceLocalizationService服务完成的。启动container是由ContainersLauncher服务完成的,而运行container是由插拔式组件ContainerExecutor完成的,YARN提供了两种ContainerExecutor实现,一种是 DefaultContainerExecutor,另一种是LinuxContainerExecutor
  2. container的生命周期是这样的:
    nm先去申请资源,然后是locallizing-downloading-localized->running-exit with failure(success)—>kill->clearnup

4.2.11 任务调度时如何查看各个job之间的依赖关系?

需要获取mapreduce的运行信息,比如运行状态,map,reduce的执行进度.

hadoop 50030端口提供web ui服务,没找到提供json或者xml的服务方式.于是,查找hadoop 50030的加载:\org\apache\hadoop\mapred\JobTracker.java:JobTracker(final JobConf conf, String identifier, Clock clock, QueueManager qm)

-->private void createInstrumentation()

           `...
            String infoAddr = 
              NetUtils.getServerAddress(conf, "mapred.job.tracker.info.bindAddress",
                                        "mapred.job.tracker.info.port",
                                        "mapred.job.tracker.http.address");
            InetSocketAddress infoSocAddr = NetUtils.createSocketAddr(infoAddr);
            String infoBindAddress = infoSocAddr.getHostName();
            int tmpInfoPort = infoSocAddr.getPort();
            this.startTime = clock.getTime();
            infoServer = new HttpServer("job", infoBindAddress, tmpInfoPort, 
                tmpInfoPort == 0, conf, aclsManager.getAdminsAcl());
            infoServer.setAttribute("job.tracker", this); 
        ...`

在这里JobTracker启了一个提供http服务的Jetty Server,并且设置了这个jetty实例infoServer的application属性job.tracker为 jobtracker本身(this)

在JobTracker运行节点上有jsp页面:$HADOOP_HOME/webapps/job/jobtracker.jsp

       `..
          JobTracker tracker = (JobTracker) application.getAttribute("job.tracker");
          ClusterStatus status = tracker.getClusterStatus();
          ClusterMetrics metrics = tracker.getClusterMetrics();
          String trackerName =
                   StringUtils.simpleHostname(tracker.getJobTrackerMachine());
          JobQueueInfo[] queues = tracker.getQueues();
          Vector<JobInProgress> runningJobs = tracker.runningJobs();
          Vector<JobInProgress> completedJobs = tracker.completedJobs();
          Vector<JobInProgress> failedJobs = tracker.failedJobs();
        ...

从这里获取了jobtracker对象,对此对象操作可获取到job的执行信息.

所以:尝试仿照jobtracker.jsp页面写满足自己需求的jobtracker_1.jsp直接拷贝一个测试一下.访问: :50030/jobtracker_1.jsp 不成功.设置WEB-INF/web.xml加入对url和servlet的对应关系,发现需要编译出org.apache.hadoop.mapred.jobtracker_1_jsp的类而且,系统自带的这些jsp文件都以_jsp.class的形式存在于hadoop-core.jar里了.

jsp编译为servlet一遍是中间件(tomcat.resin)直接做的事情,而且每种中间件编译出来的类包是不同的,比如一般放到:org.apache.jsp包下. 如何放到prg.apache.hadoop.mapred下呢?

先试一下,自己写一个jsp页面让resin,tomcat编译为class,再在web,xml中配置为他org.apache.jsp.xxxx可否.

在写jsp的时候发现,要获取JobTracker的信息.里面有很多变量诸如jobtracker.conf,这些变量都是包外不可见的.所以还不能把这个jsp对应的servlet编译为别的包下的类,只能编译到org.apache.hadoop.mapred包里.

搜索如何手动编译jsp为servlet,参考如下文章:http://blog.csdn.net/codolio/article/details/5177236通过org.apache.jasper.JspC来手工编译jsp为servlet.

java -cp /opt/jars/ant.jar:/opt/hadoop-1.0.4/lib/commons-logging-1.1.1.jar:/opt/hadoop-1.0.4/hadoop-ant-1.0.4.jar:/opt/hadoop-1.0.4/lib/commons-el-1.0.jar:/opt/hadoop-1.0.4/lib/jasper-compiler-5.5.12.jar:/opt/hadoop-1.0.4/lib/jasper-runtime-5.5.12.jar:/opt/hadoop-1.0.4/lib/servlet-api-2.5-20081211.jar:/opt/hadoop-1.0.4/lib/jsp-2.1/jsp-api-2.1.jar:/opt/hadoop-1.0.4/lib/commons-io-2.1.jar org.apache.jasper.JspC-classpath /opt/hadoop-1.0.4/hadoop-core-1.0.4.jar:/opt/hadoop-1.0.4/hadoop-ant-1.0.4.jar:/opt/hadoop-1.0.4/lib/commons-logging-1.1.1.jar:/opt/hadoop-1.0.4/lib/commons-logging-api-1.0.4.jar:/opt/hadoop-1.0.4/lib/log4j-1.2.15.jar:/opt/hadoop-1.0.4/lib/commons-io-2.1.jar -p org.apache.hadoop.mapred -compile -v -d dist -uriroot ./ -webxml dist/web.xml xxxxx.jsp注意:-p参数可以指定编译出来的servlet的package

通过这种方法应该能得到.class和.java,但我却只得到了.java,我理解可能是因为我引用的jar包不全无法通过编译,没有报错信息,很奇怪.

于是基于这个.java文件自己编译为class打成jar包,放到了$HADOOP_HOME/lib/下.配置job/WEB-INF/web.xml文件为自己编译的类

原创文章 412 获赞 264 访问量 93万+

猜你喜欢

转载自blog.csdn.net/GUDUzhongliang/article/details/105723889