MapRedece

MapRedecu代码

  1. pom
<dependency>
 <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
   <!--版本号根据自己使用的hadoop自己去maven仓库找对应的坐标,下面一样-->
    <version>2.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>2.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-core</artifactId>
    <version>2.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-yarn-common</artifactId>
    <version>2.5.2</version>
</dependency>
  1. 代码结构 (骨架)
/*1. Map
	k1:map阶段输入的key类型,实际是行偏移量
	v1:map阶段输入的value类型,实际是行内容
	k2:map输出的key类型
	v2:map输出的value类型
*/
								    //  k1		  v1     k2    v2
public class MyMaper extends Mapper<LongWritable, Text, Text, IntWritable> {
    @Override
    protected void map(LongWritable k1, Text v1, Context context) throws IOException, InterruptedException {
        //todo
    }
}

/*2. Reduce
	k1:reduce阶段的key输入类型,实际是map输出的key类型
	v1:reduce阶段的value输入类型,实际是map输出的value类型
	k2:reduce的输出的key类型
	v2:reduce的输出的value类型
*/
								  //  k1		v1    	  k2    v2
public class MyReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
    @Override
    protected void reduce(Text k2, Iterable<IntWritable> v2s, Context context) throws IOException, InterruptedException {
        //todo
    }
}

/*3. Job*/
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "MyFirstJob"); //设置job名称
//作业以jar包形式 运行
job.setJarByClass(MyMapReduce.class);
//文件输入路径
InputFormat Path path = new Path("/src/data");
TextInputFormat.addInputPath(job,path);
//设置map
Map job.setMapperClass(MyMaper.class);
//Map端输出key类型,必须和第一步的map输出key保持一致
job.setMapOutputKeyClass(Text.class);
//Map端输出value类型,必须和第一步的map输出value保持一致
job.setMapOutputValueClass(IntWritable.class);
//shuffle 默认的方式处理 无需设置

//设置reduce
reduce job.setReducerClass(MyReduce.class);
//reduce端输出key类型,和第二步reduce输出key保持一致
job.setOutputKeyClass(Text.class);
//reduce端输出value类型,和第二步reduce输出value保持一致
job.setOutputValueClass(IntWritable.class);
//输出目录一定不能存在,由MR动态创建
Path out = new Path("/dest2");
FileSystem fileSystem = FileSystem.get(conf);
fileSystem.delete(out,true);
TextOutputFormat.setOutputPath(job,out); 
// 运行job作业 
job.waitForCompletion(true);

  1. MR运行
必须运行在yarn集群上,但由于每次运行MR代码,都需要先打成jar包,然后上传到集群,在使用bin/yarn jar xxx.jar运行jar包,非常繁琐,所以为了简化操作,要借助一些插件。
//创建变量
<myJobMainClass>job类所在的路径</myJobMainClass>
//linux的ip
<target-host>192.168.0.0</target-host>
//linux上hadoop的安装路径
<target-postition>/opt/install/hadoop-2.5.2</target-postition>


<!--借用wagon插件将jar包上传到linux并执行yarn命令执行jar包-->
<extensions>
 <extension>
    <groupId>org.apache.maven.wagon</groupId>
    <artifactId>wagon-ssh</artifactId>
    <version>2.8</version>
 </extension>
</extensions>

<plugin>
	<!--maven插件-->
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
    	<!--jar包输出路径,basedir为maven的系统变量,默认输出到项目的根下-->
        <outputDirectory>${basedir}</outputDirectory>
        <archive>
            <manifest>
            	<!--mainClass-->
                <mainClass>${myJobMainClass}</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>wagon-maven-plugin</artifactId>
    <version>1.0</version>
    <configuration>
    	<!--jar包名字,系统字段-->
        <fromFile>${project.build.finalName}.jar</fromFile>
        <!--linux的账号密码及ip、上传路径-->
        <url>scp://root:123456@${target-host}${target-position}</url>
        <commands>
        	<!--先杀死原先此jar包的进程-->
            <command>pkill -f ${project.build.finalName}.jar</command>
            <!--使用hadoop命令运行上传的jar包  nohup不在控制台输出日志,指定输出到/root/nohup.out中-->
            <command>nohup ${target-postition}/bin/yarn jar ${target-position}/${project.build.finalName}.jar > /root/nohup.out 2>&amp;1 &amp;</command>
        </commands> 
        <!-- 显示运行命令的输出结果 -->
        <displayCommandOutputs>true</displayCommandOutputs>
    </configuration>
</plugin>

在idea中自定义maven命:鼠标右键-->new goal
jar:jar wagon:upload wagon:sshexec

在集群上运行wordCount案例步骤
  1.写好代码打jar包,将jar包上传到集群
  2.在hadoop下使用命令运行jar包
hadoop jar jar包名
或者yarn jar jar包名

访问8088(yarn页面)就可以看到提交的任务页面

如果 MR 造成系统宕机。此时要控制 Yarn 同时运行的任务数,和每个任务申请的最大内存。 调整参数:yarn.scheduler.maximum-allocation-mb(单个任务可申请的最多物理内存量,默认是 8192MB)

MapReduce作业中Map,Reduce的一些细节问题

  1. MapReduce中是可以没有Reduce

如果MapReduce中,只是对数据进行清洗,而不负责统计、去重的话,就没有Reduce
job.setNumReduceTasks(0); //设置reduce的个数为0,将之前关于reduce的设置注释掉
// job.setReducerClass(MyReducer.class);
// job.setOutputKeyClass(Text.class);
// job.setOutputValueClass(NullWritable.class)

  1. MapReduce中有多少个Map?

文本文件处理中,Map的数量由block决定(在不改变切片大小的情况下默认1:1;其他可见下面的切片介绍

  1. MapReduce中有多少个Reduce

3.1:Reduce个数可以设置的;默认情况reduce的个数是 1
mapreduce.job.reduces 1 //配置文件中设置
job.setNumReduceTasks(?) //代码中设置

3.2:为什么要设置多个Reduce?
a.提高MR的运行效率
b.多个Reduce的输出结果是多个文件,可以再次进行Map的处理,进行汇总
c.Partion分区 由分区决定Map输出结果,交给那个Reduce处理。默认有HashPartitioner实现 k2.hashCode()%reduceNum(2) = 0,1

  1. 程序员自定义分区算法
4.1: 自定义Partitioner
public class MyPartitioner<k2, v2> extends Partitioner<k2, v2> {
@Override
    public int getPartition(k2 key, v2 value, int numPartitions) {
        String k = key.toString();
        try {
        	//key值小于3的分到一个reduce中,否则分到另一个reduce中,这里只是测试,实际情况根据自己需求设置逻辑
            int key_i = Integer.parseInt(k);
            if (key_i <= 3) {
                return 0;
            } else {
                return 1;
            }
        } catch (Exception e) {
            return -1;
        }
    }
}

4.2:job作业设置
job.setPartitionerClass(MyPartitioner.class);

(1)如果ReduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;
(2)如果1<ReduceTask的数量<getPartition的结果数,则有一部分分区数据无处安放,会Exception;
(3)如果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,
最终也就只会产生一个结果文件 part-r-00000;
(4)分区号必须从零开始,逐一累加。


例如:假设自定义分区数为5,则
job.setNumReduceTasks(1);           会正常运行,只不过会产生一个输出文件
job.setNumReduceTasks(2);           会报错
job.setNumReduceTasks(6);          大于5,程序会正常运行,会产生空文件

  1. MapReduce中的技术器 Counter

在map中或者reduce中计数(实际没什么作用)
context.getCounter(); //此方法是重载方法,1.接收String,String 2.枚举
1.context.getCounter(“group-name”,“counter-name”).increment(1);
2.public enum MyCounter {
MY_COUNTER //定义一个枚举
}
context.getCounter(MyCounter.MY_COUNTER).increment(1);

  1. Combiner

自定义 Combiner 实现步骤:自定义一个 Combiner 继承 Reducer,重写 Reduce 方法(就是一个reduce,如果和已写的reduce逻辑一样,也可以直接使用reduce)
Combiner能够应用的前提是不能影响最终的业务逻辑
Combiner是Map端的Reduce,提前Map端作合并,从而减少传输的数据,提高效率。
默认情况是Combiner关闭
job.setCombinerClass(MyReducer.class); //设置Combiner
但如果job.setNumReduceTasks(0),即没有reduce,那么即使设置 Combiner也没什么意义,因为数据直接从map输出了,就不会走shuffle阶段

  1. MapReduce自定义数据类型(序列化和排序)

MapReduce中,Map与Reduce会进行跨JVM,跨服务器的通信,所以需要MapReduce中的数据类型进行序列化
Writable接口 Compareable接口
WritableComparable (既能排序又能序列化)
自定义的数据类型 实现WritableComparable 接口即可,然后重写write、readFields方法,实现序列化
重写compareTo 方法,实现排序
write(DataOutput out)
readFields(DataInput in)
compareto()
equals
hashcode
toString

  1. 自定义OutputFormat

自定义OutputFormat步骤

  1. 自定义一个类继承FileOutputFormat
  2. 改写RecordWriter,具体改写输出数据的方法write()
//自定义一个 LogOutputFormat 类
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class LogOutputFormat extends FileOutputFormat<Text, NullWritable> {
    @Override
    public RecordWriter<Text, NullWritable>
    getRecordWriter(TaskAttemptContext job) throws IOException,
            InterruptedException {
        //创建一个自定义的 RecordWriter 返回
        LogRecordWriter logRecordWriter = new LogRecordWriter(job);
        return logRecordWriter;
    }
}

//编写 LogRecordWriter 类
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import java.io.IOException;

public class LogRecordWriter extends RecordWriter<Text, NullWritable> {
    private FSDataOutputStream baiduOut;
    private FSDataOutputStream otherOut;

    public LogRecordWriter(TaskAttemptContext job) {
        try {
            //获取文件系统对象
            FileSystem fs = FileSystem.get(job.getConfiguration());
            //用文件系统对象创建两个输出流对应不同的目录文件
            baiduOut= fs.create(new Path("d:/hadoop/atguigu.log"));
            otherOut = fs.create(new Path("d:/hadoop/other.log"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void write(Text key, NullWritable value) throws IOException,
            InterruptedException {
        String log = key.toString();
        //根据一行的 log 数据是否包含 atguigu,判断两条输出流输出的内容
        if (log.contains("baidu")) {
            baiduOut.writeBytes(log + "\n");
        } else {
            otherOut.writeBytes(log + "\n");
        }
    }

    @Override
    public void close(TaskAttemptContext context) throws IOException,
            InterruptedException {
        //关流
        IOUtils.closeStream(baiduOut);
        IOUtils.closeStream(otherOut);
    }
}


//job作业设置:设置自定义的 outputformat
job.setOutputFormatClass(LogOutputFormat.class);
// 虽然我们自定义了outputformat,但是因为我们的outputformat继承自fileoutputformat
//而fileoutputformat要输出一个_SUCCESS文件,所以在这还得指定一个输出目录z专门输出_SUCCESS
FileOutputFormat.setOutputPath(job, new Path("D:\\logoutput"));


mapReduce中的数据类型:
IntWritable----对应java中的int
LongWritable----对应java中的long
FloatWritable----对应java中的float
Text----对应java中的string
NullWritable 空值
其他的就不一一列举了,一般都是java类型后缀Writable

Job作业的原理分析

  1. InputFormat

从HDFS读入数据,并把读入的数据封装Key Value (k1,v1)

// getSplits负责从HDFS(DataNode)中 读入文件的数据
// 把块的数据 封装 split中 保证2者 一一对应 实际存的是路径,坐标等等,等生成k、v时才会去读取数据,而不是一开始就把数据读到内存中
public abstract List getSplits(JobContext context ) throws IOException, InterruptedException;
// RecordReader 负责把数据封装成 K,V
public abstract RecordReader<K,V> createRecordReader(InputSplit split, TaskAttemptContext context ) throws IOException, InterruptedException;

在这里插入图片描述
在这里插入图片描述

  1. mapReduce的shuffle过程
    在这里插入图片描述

shuffle细划的话,分为map端shuffle、reduce端shuffle

  • Map shuffle
    在这里插入图片描述
  1. map端输出的key,value写入环形缓冲区(并且在这个阶段会给每个<k,v>对映射Partition编号,partition编号也会写入环形缓冲区)。
  2. 内存溢写到文件:环形缓冲区数据量达到阈值后就将数据写入磁盘,每次溢写都会产生一个文件(注意溢写前会先按照Key进行一次快速排序,保证数据以分区为单位聚集在一起、且区内有序;如果设置了combiner,则在写文件之前会先进行聚合)
  3. 溢写文件合并:合并之前生成的诸多临时文件(合并分区文件数据并再次排序)

map shuffle总结成自己话:当map以<key,value>对输出之后,会写入到内存中(环形缓冲区),
环形缓冲区默认大小是100MB,当数据达到缓冲区总容量的80%(阈值)时,会将内存的数据spill到本地磁盘。
数据在环形缓冲区中spill到本地磁盘之前会做分区,排序,之后再spill到本地磁盘。
因为map对数据不断的进行处理,数据会不断spill到本地磁盘,也就在磁盘上生成很多小文件。
由于最终reduce只会拿map输出的一个结果,所以会将spill到磁盘的数据进行一次合并,
将各个分区的数据合并在一起,分区合并之后再次排序。最后会形成一个文件,待reduce任务获取
  • Reduce shuffle

当map阶段数据处理完成之后,各个reduce 任务主动到已经完成的map 任务的本次磁盘中,去拉取属于自己要处理的数据,最后会形成一个文件
1)reduce对应partition的数据写入内存,根据reduce端数据内存的阈值,spill到reduce本地磁盘,形成一个个小文件
2)对文件进行合并
3)排序
4)分组(将相同key的value放在一起)

reduce shuffle总结成自己话:当map阶段数据处理完成之后,各个reduce任务主动到已经完成的map任务的
本次磁盘中,去拉取属于自己要处理的数据,最后会形成一个文件,reduce 对应partition的数据写入内存,
根据reduce端数据内存的阈值,spill到reduce本地磁盘,形成一个个小文件,然后对一个个小文件进行合并,
排序,分组(将相同key的value放在一起),最后形成reduce输入端,传递给reduce()函数,得出结果。

Job 提交流程源码详解

waitForCompletion()
submit();
// 1 建立连接
connect();
// 1)创建提交 Job 的代理
new Cluster(getConfiguration());
// (1)判断是本地运行环境还是 yarn 集群运行环境
initialize(jobTrackAddr, conf); 
// 2 提交 job
submitter.submitJobInternal(Job.this, cluster)
// 1)创建给集群提交数据的 Stag 路径
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
// 2)获取 jobid ,并创建 Job 路径
JobID jobId = submitClient.getNewJobID();
// 3)拷贝 jar 包到集群
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
// 4)计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
// 5)向 Stag 路径写 XML 配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 6)提交 Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(),

FileInputFormat 切片源码解析(input.getSplits(job))
在这里插入图片描述
FileInputFormat 切片机制
在这里插入图片描述

在这里插入图片描述

CombineTextInputFormat

CombineTextInputFormat 用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个 MapTask 处理。

// 如果不设置 InputFormat,它默认用的是 TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置 4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值