hadoop系列笔记
hadoop(一)入门、hadoop架构、集群环境搭建.
hadoop(二)HDFS概述、shell操作、客户端操作(各种API操作)以及hdfs读写流程.
hadoop(三)hdfs的NameNode和DataNode工作机制.
hadoop(四)MapReduce入门及序列化实操.
hadoop(五)MapReduce框架原理及工作机制.
hadoop(六)hadoop数据压缩、yarn架构及工作原理、hadoop企业优化.
第4章 Hadoop数据压缩
4.1 概述
- 压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在Hadood下,尤其是数据规模很大和工作负载密集的情况下,使用数据压缩显得非常重要。
- 鉴于磁盘I/O和网络带宽是Hadoop的宝贵资源,数据压缩对于节省资源、最小化磁盘I/O和网络传输非常有帮助。可以再任意MapReduce阶段启用压缩。不过,尽管压缩与解压操作的CPU开销不高,其性能的提升和资源的节省并非没有代价。
- 如果磁盘I/O和网络带宽影响了MapReduce作业性能,在任意MapReduce阶段启用压缩都可以改善端到端处理时间并减少I/O和网络流量。
- 压缩mapreduce的一种优化策略:通过压缩编码对mapper或者reducer的输出进行压缩,以减少磁盘IO,提高MR程序运行速度(但相应增加了cpu运算负担)。
- 注意:采用压缩技术减少了磁盘IO,但同时增加了CPU运算负担。所以,压缩特性运用得当能提高性能,但运用不当也可能降低性能。
- 基本原则:
(1)运算密集型的job,少用压缩
(2)IO密集型的job,多用压缩
4.2MR支持的压缩编码
压缩格式 | hadoop自带? | 算法 | 文件扩展名 | 是否可切分 | 换成压缩格式后,原来的程序是否需要修改 |
---|---|---|---|---|---|
DEFAULT | 是,直接使用 | DEFAULT | .deflate | 否 | 和文本处理一样,不需要修改 |
Gzip | 是,直接使用 | DEFAULT | .gz | 否 | 和文本处理一样,不需要修改 |
bzip2 | 是,直接使用 | bzip2 | .bz2 | 是 | 和文本处理一样,不需要修改 |
LZO | 否,需要安装 | LZO | . .lzo | 是 | 需要建索引,还需要指明输入模式。 |
Snappy | 否,需要安装 | Snappy | .snappy | 否 | 和文本处理一样,不需要修改 |
为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示
压缩格式 | 对应的编码/解码器 |
---|---|
DEFLATE | org.apache.hadoop.io.compress.DefaultCodec |
gzip | org.apache.hadoop.io.compress.GzipCodec |
bzip2 | org.apache.hadoop.io.compress.BZip2Codec |
LZO | com.hadoop.compression.lzo.LzopCodec |
Snappy | org.apache.hadoop.io.compress.SnappyCodec |
压缩性能的比较
压缩算法 | 原始文件大小 | 压缩文件大小 | 压缩速度 | 解压速度 |
---|---|---|---|---|
gzip | 8.3GB | 1.8GB | 17.5MB/s | 58MB/s |
bzip2 | 8.3GB | 1.1GB | 2.4MB/s | 9.5MB/s |
LZO | 8.3GB | 2.9GB | 49.3MB/s | 74.6MB/s |
4.3 压缩方式选择
4.3.1 Gzip压缩
- 优点:压缩率比较高,而且压缩/解压速度也比较快;hadoop本身支持,在应用中处理gzip格式的文件就和直接处理文本一样;大部分linux系统都自带gzip命令,使用方便。
- 缺点:不支持split。
- 应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用gzip压缩格式。譬如说一天或者一个小时的日志压缩成一个gzip文件,运行mapreduce程序的时候通过多个gzip文件达到并发。hive程序,streaming程序,和java写的mapreduce程序完全和文本处理一样,压缩之后原来的程序不需要做任何修改。
4.3.2 lzo压缩
- 优点:压缩/解压速度也比较快,合理的压缩率;支持split,是hadoop中最流行的压缩格式;可以在linux系统下安装lzop命令,使用方便。
- 缺点:压缩率比gzip要低一些;hadoop本身不支持,需要安装;在应用中对lzo格式的文件需要做一些特殊处理(为了支持split需要建索引,还需要指定inputformat为lzo格式)。
- 应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,lzo优点越越明显。
4.3.3 snappy压缩
- 优点:高速压缩速度和合理的压缩率。
- 缺点:不支持split;压缩率比gzip要低;hadoop本身不支持,需要安装;
- 应用场景:当mapreduce作业的map输出的数据比较大的时候,作为map到reduce的中间数据的压缩格式;或者作为一个mapreduce作业的输出和另外一个mapreduce作业的输入。
4.3.4 bzip2压缩
- 优点:支持split;具有很高的压缩率,比gzip压缩率都高;hadoop本身支持,但不支持native;在linux系统下自带bzip2命令,使用方便。
- 缺点:压缩/解压速度慢;不支持native。
- 应用场景:适合对速度要求不高,但需要较高的压缩率的时候,可以作为mapreduce作业的输出格式;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持split,而且兼容之前的应用程序(即应用程序不需要修改)的情况。
4.3.5 如何选择压缩格式?
- Hadoop应用处理的数据集非常大,因此需要借助于压缩。使用哪种压缩格式与待处理的文件的大小、格式和所使用的工具相关。下面我们给出了一些建议,大致是按照效率从高到低排序的。
- 1)使用容器文件格式,例如顺序文件、RCFile或者Avro 数据文件,所有这些文件格式同时支持压缩和切分。通常最好与一个快速压缩工具联合使用,例如LZO,LZ4或者 Snappy。
- 2)使用支持切分的压缩格式,例如bzip2(尽管bzip2 非常慢),或者使用通过索引实现切分的压缩格式,例如LZO。
- 3)在应用中将文件切分成块,并使用任意一种压缩格式为每个数据块建立压缩文件(不论它是否支持切分)。这种情况下,需要合理选择数据块的大小,以确保压缩后数据块的大小近似与HDFS块的大小。
- 4)存储未经压缩的文件。
对大文件来说,不要使用不支持切分整个文件的压缩格式,因为会失去数据的本地特性,进而造成MapReduce应用效率低下。
4.4 采用压缩的位置
压缩可以在MapReduce作用的任意阶段启用。
- 1)输入压缩:
在有大量数据并计划重复处理的情况下,应该考虑对输入进行压缩。然而,你无须显示指定使用的编解码方式。Hadoop自动检查文件扩展名,如果扩展名能够匹配,就会用恰当的编解码方式对文件进行压缩和解压。否则,Hadoop就不会使用任何编解码器。 - 2)压缩mapper输出:
当map任务输出的中间数据量很大时,应考虑在此阶段采用压缩技术。这能显著改善内部数据Shuffle过程,而Shuffle过程在Hadoop处理过程中是资源消耗最多的环节。如果发现数据量大造成网络传输缓慢,应该考虑使用压缩技术。可用于压缩mapper输出的快速编解码器包括LZO或者Snappy。
注:LZO是供Hadoop压缩数据用的通用压缩编解码器。其设计目标是达到与硬盘读取速度相当的压缩速度,因此速度是优先考虑的因素,而不是压缩率。与gzip编解码器相比,它的压缩速度是gzip的5倍,而解压速度是gzip的2倍。同一个文件用LZO压缩后比用gzip压缩后大50%,但比压缩前小25%~50%。这对改善性能非常有利,map阶段完成时间快4倍。 - 3)压缩reducer输出:
在此阶段启用压缩技术能够减少要存储的数据量,因此降低所需的磁盘空间。当mapreduce作业形成作业链条时,因为第二个作业的输入也已压缩,所以启用压缩同样有效。
4.5 压缩配置参数
要在Hadoop中启用压缩,可以配置如下参数:
参数 | 默认值 | 阶段 | 建议 |
---|---|---|---|
io.compression.codecs(在core-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec | 输入压缩 | Hadoop使用文件扩展名判断是否支持某种编解码器 |
mapreduce.map.output.compress(在mapred-site.xml中配置) | false | mapper输出 | 这个参数设为true启用压缩 |
mapreduce.map.output.compress.codec(在mapred-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec | mapper输出 | 企业多使用LZO或Snappy编解码器在此阶段压缩数据 |
mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置) | false | reducer输出 | 这个参数设为true启用压缩 |
mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置) | org.apache.hadoop.io.compress. DefaultCodec | reducer输出 | 使用标准工具或者编解码器,如gzip和bzip2 |
mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置) | RECORD | reducer输出 | SequenceFile输出使用的压缩类型:NONE和BLOCK |
4.6 压缩实操案例
4.6.1 数据流的压缩和解压缩
- 要想对正在被写入一个输出流的数据进行压缩,我们可以使用createOutputStream(OutputStream)方法创建一个CompressionOutputStream,将其以压缩格式写入底层的流。
- 相反,要想对从一个输入流读取而来的数据进行解压缩,我们可以调用用createInputStream(InputStream)方法,创建一个CompressionInputStream,从而从底层的流读取到未压缩的数据。
- 测试一下如下压缩方式:
DEFLATE org.apache.hadoop.io.compress.DefaultCodec
gzip org.apache.hadoop.io.compress.GzipCodec
bzip2 org.apache.hadoop.io.compress.BZip2Codec
package com.lyj.compress;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionCodecFactory;
import org.apache.hadoop.io.compress.CompressionInputStream;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.apache.hadoop.util.ReflectionUtils;
import org.omg.IOP.Codec;
import java.io.*;
/**
* @author liuyongjun
* @date 2020-07-09-22:45
*/
public class TestCompress {
public static void main(String[] args) throws Exception {
//测试压缩 compress方法两参数,第一个是要压缩文件路径,第二个是压缩格式
compress("d:/2.txt","org.apache.hadoop.io.compress.BZip2Codec");
decompress("d:/2.txt.bz2");
}
// 1、压缩
private static void compress(String fileName, String method) throws Exception {
//(1)获取输入流
FileInputStream fis = new FileInputStream(new File(fileName));
Class<?> aClass = Class.forName(method);
CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(aClass, new Configuration());
//(2)获取输出流
//注意输出流加后缀
FileOutputStream fos = new FileOutputStream(fileName + codec.getDefaultExtension());
CompressionOutputStream cos = codec.createOutputStream(fos);
//(3)流的对拷
//最后的false是是否关闭输入输出流,我们选择false,最后再关
IOUtils.copyBytes(fis, cos, 1024*1024, false);
//(4)关闭资源
cos.close();
fos.close();
fis.close();
}
// 2、解压缩
private static void decompress(String fileName) throws IOException {
//(0)校验是否能解压缩
CompressionCodecFactory factory = new CompressionCodecFactory(new Configuration());
CompressionCodec codec = factory.getCodec(new Path(fileName));
if(codec == null){
System.out.println("cannot find codec for file " + fileName);
return;
}
// (1)获取输入流.
FileInputStream fis = new FileInputStream(new File(fileName));
CompressionInputStream cis = codec.createInputStream(fis);
// (2)获取输出流
FileOutputStream fos = new FileOutputStream(new File(fileName + ".decoded"));
// (3)流的对拷
IOUtils.copyBytes(cis, fos, 1024*1024, false);
// (4)关闭资源
cis.close();
fos.close();
fis.close();
}
}
4.6.2 Map输出端采用压缩
- 即使你的MapReduce的输入输出文件都是未压缩的文件,你仍然可以对Map任务的中间结果输出做压缩,因为它要写在硬盘并且通过网络传输到Reduce节点,对其压缩可以提高很多性能,这些工作只要设置两个属性即可,我们来看下代码怎么设置。
在Driver中加入两个代码:
// 开启map端输出压缩
configuration.setBoolean("mapreduce.map.output.compress", true);
// 设置map端输出压缩方式
configuration.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);
- mapper和reducer保持不变
- 输出没有任何变化,Map和Reduce输出端进行的压缩,并不会影响最终输出结果,只是在传输过程中提高了IO传输效率
4.6.3 Reduce输出端采用压缩
- 也是设置两个属性
- 在driver中设置即可
// 设置reduce端输出压缩开启
FileOutputFormat.setCompressOutput(job, true);
// 设置压缩的方式
FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
第5章 Yarn资源调度器
- Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源,相当于一个分布式的操作系统平台,而MapReduce等运算程序则相当于运行于操作系统之上的应用程序。
5.1 Yarn基本架构
- YARN主要由ResourceManager、NodeManager、ApplicationMaster和Container等组件构成,如图所示:
5.3 Yarn工作机制
注意下图有一个MRAPPMaster
MRAppMaster是MapReduce的ApplicationMaster实现,它使得MapReduce计算框架可以运行于YARN之上。在YARN中,MRAppMaster负责管理MapReduce作业的生命周期,包括创建MapReduce作业,向ResourceManager申请资源,与NodeManage通信要求其启动Container,监控作业的运行状态,当任务失败时重新启动任务等。
- 1.Yarn运行机制,如图
- 2.工作机制详解
(1)MR程序提交到客户端所在的节点。
(2)YarnRunner向ResourceManager申请一个Application。
(3)RM将该应用程序的资源路径返回给YarnRunner。
(4)该程序将运行所需资源提交到HDFS上。
(5)程序资源提交完毕后,申请运行mrAppMaster。
(6)RM将用户的请求初始化成一个Task。
(7)其中一个NodeManager领取到Task任务。
(8)该NodeManager创建容器Container,并产生MRAppmaster。
(9)Container从HDFS上拷贝资源到本地。
(10)MRAppmaster向RM 申请运行MapTask资源。
(11)RM将运行MapTask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务并创建容器。
(12)MR向两个接收到任务的NodeManager发送程序启动脚本,这两个NodeManager分别启动MapTask,MapTask对数据分区排序。
(13)MrAppMaster等待所有MapTask运行完毕后,向RM申请容器,运行ReduceTask。
(14)ReduceTask向MapTask获取相应分区的数据。
(15)程序运行完毕后,MR会向RM申请注销自己。
5.4 作业提交全过程
- 1.作业提交过程之YARN,如图所示:
作业提交全过程详解
(1)作业提交
第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。
第2步:Client向RM申请一个作业id。
第3步:RM给Client返回该job资源的提交路径和作业id。
第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。
第5步:Client提交完资源后,向RM申请运行MrAppMaster。
(2)作业初始化
第6步:当RM收到Client的请求后,将该job添加到容量调度器中。
第7步:某一个空闲的NM领取到该Job。
第8步:该NM创建Container,并产生MRAppmaster。
第9步:下载Client提交的资源到本地。
(3)任务分配
第10步:MrAppMaster向RM申请运行多个MapTask任务资源。
第11步:RM将运行MapTask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务并创建容器。
(4)任务运行
第12步:MR向两个接收到任务的NodeManager发送程序启动脚本,这两个NodeManager分别启动MapTask,MapTask对数据分区排序。
第13步:MrAppMaster等待所有MapTask运行完毕后,向RM申请容器,运行ReduceTask。
第14步:ReduceTask向MapTask获取相应分区的数据。
第15步:程序运行完毕后,MR会向RM申请注销自己。
(5)进度和状态更新
YARN中的任务将其进度和状态(包括counter)返回给应用管理器, 客户端每秒(通过mapreduce.client.progressmonitor.pollinterval设置)向应用管理器请求进度更新, 展示给用户。
(6)作业完成
除了向应用管理器请求作业进度外, 客户端每5秒都会通过调用waitForCompletion()来检查作业是否完成。时间间隔可以通过mapreduce.client.completion.pollinterval来设置。作业完成之后, 应用管理器和Container会清理工作状态。作业的信息会被作业历史服务器存储以备之后用户核查。 - 2.作业提交过程之MapReduce,如图
- 3.作业提交过程之HDFS读数据
- 4.作业提交过程之HDFS写数据
5.5 资源调度器
- 目前,Hadoop作业调度器主要有三种:FIFO、Capacity Scheduler和Fair Scheduler。Hadoop2.7.2默认的资源调度器是Capacity Scheduler。
- 具体设置详见:yarn-default.xml文件
<property>
<description>The class to use as the resource scheduler.</description>
<name>yarn.resourcemanager.scheduler.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value>
</property>
- 1.先进先出调度器(FIFO),如图
- 2.容量调度器(Capacity Scheduler),如图:
- 3.公平调度器(Fair Scheduler),如图:
5.6 任务的推测执行
- 1.作业完成时间取决于最慢的任务完成时间
一个作业由若干个Map任务和Reduce任务构成。因硬件老化、软件Bug等,某些任务可能运行非常慢。
思考:系统中有99%的Map任务都完成了,只有少数几个Map老是进度很慢,完不成,怎么办? - 2.推测执行机制
发现拖后腿的任务,比如某个任务运行速度远慢于任务平均速度。为拖后腿任务启动一个备份任务,同时运行。谁先运行完,则采用谁的结果。 - 3.执行推测任务的前提条件
(1)每个Task只能有一个备份任务
(2)当前Job已完成的Task必须不小于0.05(5%)
(3)开启推测执行参数设置。mapred-site.xml文件中默认是打开的。
<property>
<name>mapreduce.map.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some map tasks may be executed in parallel.</description>
</property>
<property>
<name>mapreduce.reduce.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some reduce tasks may be executed in parallel.</description>
</property>
- 4.不能启用推测执行机制情况
(1)任务间存在严重的负载倾斜;
(2)特殊任务,比如任务向数据库中写数据。 - 5.算法原理,如图所示:
第6章 Hadoop企业优化
6.1 MapReduce 跑的慢的原因
- Mapreduce 程序效率的瓶颈在于两点:
- 1)计算机性能
CPU、内存、磁盘健康、网络 - 2)I/O 操作优化
(1)数据倾斜
(2)map和reduce数设置不合理
(3)reduce等待过久
(4)小文件过多
(5)大量的不可分块的超大文件
(6)spill次数过多
(7)merge次数过多等。
- 1)计算机性能
6.2 MapReduce优化方法
- MapReduce优化方法主要从以下六个方面考虑:数据输入、Map阶段、Reduce阶段、IO传输、数据倾斜问题和常用的调优参数。
6.2.1 数据输入
- (1)合并小文件:在执行mr任务前将小文件进行合并,大量的小文件会产生大量的map任务,增大map任务装载次数,而任务的装载比较耗时,从而导致 mr 运行较慢。
- (2)采用ConbinFileInputFormat来作为输入,解决输入端大量小文件场景。
6.2.2 Map阶段
- 1)减少溢写spill次数:通过调整io.sort.mb及sort.spill.percent(默认80%)参数值,增大触发spill的内存上限,减少spill次数,从而减少磁盘 IO。
- 2)减少合并merge次数:通过调整io.sort.factor参数,增大merge的文件数目,减少merge的次数,从而缩短mr处理时间。
- 3)在 map 之后,不影响业务逻辑前提下,先进行combine处理,减少 I/O。
6.2.3 Reduce阶段
- 1)合理设置map和reduce数:两个都不能设置太少,也不能设置太多。太少,会导致task等待,延长处理时间;太多,会导致 map、reduce任务间竞争资源,造成处理超时等错误。
- 2)设置map、reduce共存:调整slowstart.completedmaps参数,使map运行到一定程度后,reduce也开始运行,减少reduce的等待时间。
- 3)规避使用reduce,因为Reduce在用于连接数据集的时候将会产生大量的网络消耗。
- 4)合理设置reduc端的buffer,默认情况下,数据达到一个阈值的时候,buffer中的数据就会写入磁盘,然后reduce会从磁盘中获得所有的数据。也就是说,buffer和reduce是没有直接关联的,中间多个一个写磁盘->读磁盘的过程,既然有这个弊端,那么就可以通过参数来配置,使得buffer中的一部分数据可以直接输送到reduce,从而减少IO开销:mapred.job.reduce.input.buffer.percent,默认为0.0。当值大于0的时候,会保留指定比例的内存读buffer中的数据直接拿给reduce使用。这样一来,设置buffer需要内存,读取数据需要内存,reduce计算也要内存,所以要根据作业的运行情况进行调整。
6.2.4 IO传输
- 1)采用数据压缩的方式,减少网络IO的的时间。比如说安装Snappy和LZOP压缩编码器。
- 2)使用SequenceFile二进制文件,作为中间值进行二进制传输。
6.2.5 数据倾斜问题
- 1)数据倾斜现象
数据频率倾斜——某一个区域的数据量要远远大于其他区域。
数据大小倾斜——部分记录的大小远远大于平均值。 - 2)减少数据倾斜的方法(具体看处理数据倾斜问题.)
方法1:抽样和范围分区
可以通过对原始数据进行抽样得到的结果集来预设分区边界值。
说白了就是分区不合理时选择再分区
方法2:自定义分区
另一个抽样和范围分区的替代方案是基于输出键的背景知识进行自定义分区。例如,如果map输出键的单词来源于一本书。其中大部分必然是省略词(stopword)。那么就可以将自定义分区将这部分省略词发送给固定的一部分reduce实例。而将其他的都发送给剩余的reduce实例。
方法3:Combine
使用Combine可以大量地减小数据频率倾斜和数据大小倾斜。在可能的情况下,combine的目的就是聚合并精简数据。
方法4:采用Map Join,尽量避免Reduce Join。
6.2.6 常用的调优参数
- 1.资源相关参数
- (1)以下参数是在用户自己的MR应用程序中配置就可以生效(mapred-default.xml)
配置参数 | 参数说明 |
---|---|
mapreduce.map.memory.mb | 一个MapTask可使用的资源上限(单位:MB),默认为1024。如果MapTask实际使用的资源量超过该值,则会被强制杀死。 |
mapreduce.reduce.memory.mb | 一个ReduceTask可使用的资源上限(单位:MB),默认为1024。如果ReduceTask实际使用的资源量超过该值,则会被强制杀死。 |
mapreduce.map.cpu.vcores 每个MapTask可使用的最多cpu core数目,默认值: 1 | |
mapreduce.reduce.cpu.vcores | 每个ReduceTask可使用的最多cpu core数目,默认值: 1 |
mapreduce.reduce.shuffle.parallelcopies | 每个Reduce去Map中取数据的并行数。默认值是5 |
mapreduce.reduce.shuffle.merge.percent | Buffer中的数据达到多少比例开始写入磁盘。默认值0.66 |
mapreduce.reduce.shuffle.input.buffer.percent | Buffer大小占Reduce可用内存的比例。默认值0.7 |
mapreduce.reduce.input.buffer.percent | 指定多少比例的内存用来存放Buffer中的数据,默认值是0.0 |
- (2)应该在YARN启动之前就配置在服务器的配置文件中才能生效(yarn-default.xml)
配置参数 | 参数说明 |
---|---|
yarn.scheduler.minimum-allocation-mb | 给应用程序Container分配的最小内存,默认值:1024 |
yarn.scheduler.maximum-allocation-mb | 给应用程序Container分配的最大内存,默认值:8192 |
yarn.scheduler.minimum-allocation-vcores | 每个Container申请的最小CPU核数,默认值:1 |
yarn.scheduler.maximum-allocation-vcores | 每个Container申请的最大CPU核数,默认值:32 |
yarn.nodemanager.resource.memory-mb | 给Containers分配的最大物理内存,默认值:8192 |
(3)Shuffle性能优化的关键参数,应在YARN启动之前就配置好(mapred-default.xml)
配置参数 | 参数说明 |
---|---|
mapreduce.task.io.sort.mb | Shuffle的环形缓冲区大小,默认100m |
mapreduce.map.sort.spill.percent | 环形缓冲区溢出的阈值,默认80% |
2.容错相关参数(MapReduce性能优化)
配置参数 | 参数说明 |
---|---|
mapreduce.map.maxattempts | 每个Map Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 |
mapreduce.reduce.maxattempts | 每个Reduce Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 |
mapreduce.task.timeout | Task超时时间,经常需要设置的一个参数,该参数表达的意思为:如果一个Task在一定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该Task处于Block状态,可能是卡住了,也许永远会卡住,为了防止因为用户程序永远Block住不退出,则强制设置了一个该超时时间(单位毫秒),默认是600000。如果你的程序对每条输入数据的处理时间过长(比如会访问数据库,通过网络拉取数据等),建议将该参数调大,该参数过小常出现的错误提示是“AttemptID:attempt_14267829456721_123456_m_000224_0 Timed out after 300 secsContainer killed by the ApplicationMaster.”。 |
- 记不住没关系,提起来有印象就行,另外开发的时候可以照着改
6.3 HDFS小文件优化方法
6.3.1 HDFS小文件弊端
- HDFS上每个文件都要在NameNode上建立一个索引,这个索引的大小约为150byte,这样当小文件比较多的时候,就会产生很多的索引文件,一方面会大量占用NameNode的内存空间,另一方面就是索引文件过大使得索引速度变慢。
6.3.2 HDFS小文件解决方案
- 小文件的优化无非以下几种方式:
(1)在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS。
(2)在业务处理之前,在HDFS上使用MapReduce程序对小文件进行合并。
(3)在MapReduce处理时,可采用CombineTextInputFormat提高效率。 - 具体介绍:
- 1)Hadoop Archive:
是一个高效地将小文件放入HDFS块中的文件存档工具,它能够将多个小文件打包成一个HAR文件,这样在减少namenode内存使用的同时。 - 2)Sequence file:
sequence file由一系列的二进制key/value组成,如果key为文件名,value为文件内容,则可以将大批小文件合并成一个大文件。 - 3)CombineFileInputFormat:
CombineFileInputFormat是一种新的inputformat,用于将多个文件合并成一个单独的split,另外,它会考虑数据的存储位置。 - 4)开启JVM重用
对于大量小文件Job,可以开启JVM重用会减少45%运行时间。
JVM重用理解:一个map运行一个jvm,重用的话,在一个map在jvm上运行完毕后,jvm继续运行其他jvm
具体设置:mapreduce.job.jvm.numtasks值在10-20之间。
- 1)Hadoop Archive:
第七章 MapReduce扩展案例
7.1 倒排索引案例(多job串联)
- 1.需求分析
需要两次处理,第一次的处理结果作为第二次处理的输入,形成多job串联 - 3.第一次处理(第一个job)
(1)第一次处理,编写OneIndexMapper类
package com.lyj.index;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-20:19
*/
public class OneIndexMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
String name;
Text k = new Text();
IntWritable v = new IntWritable();
@Override
protected void setup(Context context) throws IOException, InterruptedException {
//获取文件名称.
FileSplit inputSplit = (FileSplit) context.getInputSplit();
name = inputSplit.getPath().getName();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取1行
String line = value.toString();
// 2 切割
String[] fields = line.split(" ");
for (String word : fields) {
// 3 拼接
k.set(word+"--"+name);
v.set(1);
// 4 写出
context.write(k, v);
}
}
}
(2)第一次处理,编写OneIndexReducer类
package com.lyj.index;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-20:27
*/
public class OneIndexReducer extends Reducer<Text, IntWritable,Text, IntWritable> {
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
// 1 累加求和
for(IntWritable value: values){
sum +=value.get();
}
v.set(sum);
// 2 写出
context.write(key, v);
}
}
(3)第一次处理,编写OneIndexDriver类
package com.lyj.index;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-20:33
*/
public class OneIndexDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(OneIndexDriver.class);
job.setMapperClass(OneIndexMapper.class);
job.setReducerClass(OneIndexReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.setInputPaths(job,new Path("d:/input"));
FileOutputFormat.setOutputPath(job,new Path("d:/output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
(4)查看第一次输出结果
- 4.第二次处理
- 以第一次输出为输入
(1)第二次处理,编写TwoIndexMapper类
package com.lyj.index;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-21:16
*/
public class TwoIndexMapper extends Mapper<LongWritable, Text,Text,Text> {
Text k = new Text();
Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取1行数据
String line = value.toString();
// 2用“--”切割
String[] fields = line.split("--");
k.set(fields[0]);
v.set(fields[1]);
// 3 输出数据
context.write(k, v);
}
}
(2)第二次处理,编写TwoIndexReducer类
package com.lyj.index;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-21:19
*/
public class TwoIndexReducer extends Reducer<Text,Text,Text,Text> {
Text v = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
// atguigu a.txt 3
// atguigu b.txt 2
// atguigu c.txt 2
// atguigu c.txt-->2 b.txt-->2 a.txt-->3
StringBuilder sb = new StringBuilder();
// 1 拼接
for (Text value : values) {
sb.append(value.toString().replace("\t", "-->") + "\t");
}
v.set(sb.toString());
// 2 写出
context.write(key, v);
}
}
(3)第二次处理,编写TwoIndexDriver类
package com.lyj.index;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-21:24
*/
public class TwoIndexDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(TwoIndexDriver.class);
job.setMapperClass(TwoIndexMapper.class);
job.setReducerClass(TwoIndexReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path("d:/output"));
FileOutputFormat.setOutputPath(job, new Path("d:/output2"));
boolean result = job.waitForCompletion(true);
System.exit(result?0:1);
}
}
(4)第二次查看最终结果
7.2 TopN案例
- 1.需求
对例2.3输出结果进行加工,输出流量使用量在前10的用户信息 - 2.需求分析
- 3.实现代码
(1)编写FlowBean类
package com.lyj.topn;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-20:03
*/
public class FlowBean implements WritableComparable<FlowBean> {
private long upFlow;
private long downFlow;
private long sumFlow;
public FlowBean() {
super();
}
public FlowBean(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
}
public int compareTo(FlowBean o) {
int result;
//根据sumflow从大到小
if (this.sumFlow > o.getSumFlow()) {
result = -1;
}else if (this.sumFlow < o.getSumFlow()) {
result = 1;
}else {
result = 0;
}
return result;
}
public void write(DataOutput out) throws IOException {
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}
public void readFields(DataInput in) throws IOException {
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
public void set(long downFlow2, long upFlow2) {
downFlow = downFlow2;
upFlow = upFlow2;
sumFlow = downFlow2 + upFlow2;
}
public long getUpFlow() {
return upFlow;
}
public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}
public long getDownFlow() {
return downFlow;
}
public void setDownFlow(long downFlow) {
this.downFlow = downFlow;
}
public long getSumFlow() {
return sumFlow;
}
public void setSumFlow(long sumFlow) {
this.sumFlow = sumFlow;
}
}
(2)编写TopNMapper类
package com.lyj.topn;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
import java.util.Iterator;
import java.util.TreeMap;
/**
* @author liuyongjun
* @date 2020-07-13-21:47
*/
public class TopNMapper extends Mapper<LongWritable, Text, FlowBean, Text> {
// 定义一个TreeMap作为存储数据的容器(天然按key排序)
private TreeMap<FlowBean, Text> flowMap = new TreeMap<FlowBean, Text>();
private FlowBean kBean;
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
kBean = new FlowBean();
Text v = new Text();
// 1 获取一行
String line = value.toString();
// 2 切割
String[] fields = line.split("\t");
// 3 封装数据
String phoneNum = fields[0];
long upFlow = Long.parseLong(fields[1]);
long downFlow = Long.parseLong(fields[2]);
long sumFlow = Long.parseLong(fields[3]);
kBean.setDownFlow(downFlow);
kBean.setUpFlow(upFlow);
kBean.setSumFlow(sumFlow);
v.set(phoneNum);
// 4 向TreeMap中添加数据
flowMap.put(kBean, v);
// 5 限制TreeMap的数据量,超过10条就删除掉流量最小的一条数据
if (flowMap.size() > 10) {
// flowMap.remove(flowMap.firstKey());
flowMap.remove(flowMap.lastKey());
}
}
@Override
protected void cleanup(Context context) throws IOException, InterruptedException {
// 6 遍历treeMap集合,输出数据
Iterator<FlowBean> bean = flowMap.keySet().iterator();
while (bean.hasNext()) {
FlowBean k = bean.next();
context.write(k, flowMap.get(k));
}
}
}
(3)编写TopNReducer类
package com.lyj.topn;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.Iterator;
import java.util.TreeMap;
/**
* @author liuyongjun
* @date 2020-07-13-22:13
*/
public class TopNReducer extends Reducer<FlowBean, Text,Text,FlowBean> {
// 定义一个TreeMap作为存储数据的容器(天然按key排序)
TreeMap<FlowBean, Text> flowMap = new TreeMap<FlowBean, Text>();
@Override
protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text value : values) {
FlowBean bean = new FlowBean();
bean.set(key.getDownFlow(), key.getUpFlow());
// 1 向treeMap集合中添加数据
flowMap.put(bean, new Text(value));
// 2 限制TreeMap数据量,超过10条就删除掉流量最小的一条数据
if (flowMap.size() > 10) {
// flowMap.remove(flowMap.firstKey());
flowMap.remove(flowMap.lastKey());
}
}
}
@Override
protected void cleanup(Context context) throws IOException, InterruptedException {
// 3 遍历集合,输出数据
Iterator<FlowBean> it = flowMap.keySet().iterator();
while (it.hasNext()) {
FlowBean v = it.next();
context.write(new Text(flowMap.get(v)), v);
}
}
}
(4)编写TopNDriver类
package com.lyj.topn;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-22:22
*/
public class TopNDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取配置信息,或者job对象实例
Job job = Job.getInstance(new Configuration());
// 6 指定本程序的jar包所在的本地路径
job.setJarByClass(TopNDriver.class);
// 2 指定本业务job要使用的mapper/Reducer业务类
job.setMapperClass(TopNMapper.class);
job.setReducerClass(TopNReducer.class);
// 3 指定mapper输出数据的kv类型
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(Text.class);
// 4 指定最终输出的数据的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 5 指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job, new Path("d:/output4"));
FileOutputFormat.setOutputPath(job, new Path("d:/output5"));
// 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
7.3 找博客共同好友案例
- 1.需求
以下是博客的好友列表数据,冒号前是一个用户,冒号后是该用户的所有好友(数据中的好友关系是单向的)
求出哪些人两两之间有共同好友,及他俩的共同好友都有谁?
A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
D:A,E,F,L
E:B,C,D,M,L
F:A,B,C,D,E,O,M
G:A,C,D,E,F
H:A,C,D,E,O
I:A,O
J:B,O
K:A,C,D
L:D,E,F
M:E,F,G
O:A,H,I,J
- 2.需求分析
先求出A、B、C、….等是谁的好友
第一次输出结果
A I,K,C,B,G,F,H,O,D,
B A,F,J,E,
C A,E,B,H,F,G,K,
D G,C,K,A,L,F,E,H,
E G,M,L,H,A,F,B,D,
F L,M,D,C,G,A,
G M,
H O,
I O,C,
J O,
K B,
L D,E,
M E,F,
O A,H,I,J,F,
第二次输出两两共同好友结果
A-B E C
A-C D F
A-D E F
A-E D B C
A-F O B C D E
A-G F E C D
A-H E C D O
A-I O
A-J O B
A-K D C
A-L F E D
A-M E F
B-C A
B-D A E
B-E C
B-F E A C
B-G C E A
B-H A E C
B-I A
B-K C A
B-L E
B-M E
B-O A
C-D A F
C-E D
C-F D A
C-G D F A
C-H D A
C-I A
C-K A D
C-L D F
C-M F
C-O I A
D-E L
D-F A E
D-G E A F
D-H A E
D-I A
D-K A
D-L E F
D-M F E
D-O A
E-F D M C B
E-G C D
E-H C D
E-J B
E-K C D
E-L D
F-G D C A E
F-H A D O E C
F-I O A
F-J B O
F-K D C A
F-L E D
F-M E
F-O A
G-H D C E A
G-I A
G-K D A C
G-L D F E
G-M E F
G-O A
H-I O A
H-J O
H-K A C D
H-L D E
H-M E
H-O A
I-J O
I-K A
I-O A
K-L D
K-O A
L-M E F
- 3 找共同好友思路串联
找两个人的共同好友 那么共同好友就是两个人关联的对象
两个人站在一块 共同指认好友
源数据格式:
人1 :好友a,好友b,好友c 。。。
人2 :好友a,好友c,好友f 。。。
第一次map
1: 把数据都切了 切成 人 、 友[ 好友数组 ] 的形式;
2: 遍历好友数组 写出数据 格式为: <友a , 人1> ,<友b ,人1>。。。。。
Reduce阶段:
Map传过来的数据为 <友a,人1> <友a,人2> <友a,人3> <友a,人4>。。。。。
遍历values汇总成 友a:人1,人2,人3 。。。。。的格式
再写出
第二次:
Map阶段
1:切分数据
每行数据切成 友 、 人[ 人数组 ] 的形式
2:遍历数组 人 两层for循环 最终得到的形式是<人m ,人n> 这两个人共同的好友都为 友a
同理 下条读入的数据 切成后也有可能是 <人m ,人n> 友b
3:写出数据
以 key =<人m ,人n> ; value = 友 的形式写出
Reduce阶段
1:Map传输来的数据格式为 <人m ,人n> 友
2:汇总 遍历所有value 得到 <人m ,人n> 共同的好友
3: 写出数据 <人m ,人n>: 友a ,友b。。。。
- 4.代码分析
(1)第一次Mapper类
package com.lyj.friends;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-22:52
*/
public class OneShareFriendsMapper extends Mapper<LongWritable, Text, Text, Text> {
private Text k = new Text();
private Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行 A:B,C,D,F,E,O
String[] split = value.toString().split(":");
// 3 获取person和好友
v.set(split[0]);
String[] friends = split[1].split(",");
// 4写出去
for(String friend: friends){
k.set(friend);
// 输出 <好友,人>
context.write(k, v);
}
}
}
(2)第一次Reducer类
package com.lyj.friends;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-22:54
*/
public class OneShareFriendsReducer extends Reducer<Text, Text, Text, Text> {
private StringBuffer sb = new StringBuffer();
private Text v =new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//清零
sb.delete(0,sb.length());
//1 拼接
for(Text value: values){
sb.append(value.toString()).append(",");
}
v.set(sb.toString());
//2 写出
context.write(key, v);
}
}
(4)第二次Mapper类
package com.lyj.friends;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
import java.util.Arrays;
/**
* @author liuyongjun
* @date 2020-07-13-23:14
*/
public class TwoShareFriendsMapper extends Mapper<LongWritable, Text, Text, Text> {
private Text k = new Text();
private Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// A I,K,C,B,G,F,H,O,D,
// 友 人,人,人
String line = value.toString();
String[] friend_persons = line.split("\t");
v.set(friend_persons[0]);
String[] persons = friend_persons[1].split(",");
Arrays.sort(persons);
for (int i = 0; i < persons.length - 1; i++) {
for (int j = i + 1; j < persons.length; j++) {
// 发出 <人-人,好友> ,这样,相同的“人-人”对的所有好友就会到同1个reduce中去
k.set(persons[i] + "-" + persons[j]);
context.write(k, v);
}
}
}
}
(5)第二次Reducer类
package com.lyj.friends;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-23:18
*/
public class TwoShareFriendsReducer extends Reducer<Text, Text, Text, Text> {
private StringBuffer sb = new StringBuffer();
private Text v =new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//清零
sb.delete(0,sb.length());
//1 拼接
for(Text value: values){
sb.append(value.toString()).append(",");
}
v.set(sb.toString());
//2 写出
context.write(key, v);
}
}
(6)Driver类
package com.lyj.friends;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* @author liuyongjun
* @date 2020-07-13-23:43
*/
public class ShareFriendsDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job1 = Job.getInstance(new Configuration());
// 2 指定jar包运行的路径
job1.setJarByClass(OneShareFriendsDriver.class);
// 3 指定map/reduce使用的类
job1.setMapperClass(OneShareFriendsMapper.class);
job1.setReducerClass(OneShareFriendsReducer.class);
// 4 指定map输出的数据类型
job1.setMapOutputKeyClass(Text.class);
job1.setMapOutputValueClass(Text.class);
// 5 指定最终输出的数据类型
job1.setOutputKeyClass(Text.class);
job1.setOutputValueClass(Text.class);
// 6 指定job的输入原始所在目录
FileInputFormat.setInputPaths(job1, new Path("d:/input"));
FileOutputFormat.setOutputPath(job1, new Path("d:/output"));
// 7 提交
boolean b = job1.waitForCompletion(true);
if(b){
// 1 获取job对象
Job job2 = Job.getInstance(new Configuration());
// 2 指定jar包运行的路径
job2.setJarByClass(TwoShareFriendsDriver.class);
// 3 指定map/reduce使用的类
job2.setMapperClass(TwoShareFriendsMapper.class);
job2.setReducerClass(TwoShareFriendsReducer.class);
// 4 指定map输出的数据类型
job2.setMapOutputKeyClass(Text.class);
job2.setMapOutputValueClass(Text.class);
// 5 指定最终输出的数据类型
job2.setOutputKeyClass(Text.class);
job2.setOutputValueClass(Text.class);
// 6 指定job的输入原始所在目录
FileInputFormat.setInputPaths(job2, new Path("d:/output"));
FileOutputFormat.setOutputPath(job2, new Path("d:/output2"));
// 7 提交
boolean b2 = job2.waitForCompletion(true);
System.exit(b2 ? 0 : 1);
}
}
}
结果看上面