MapReduce定义
- MapReduce是一个分布式运算程序放入编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
- MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上。
MapReduce的优缺点
1. MapReduce易于编程
它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点是的MapReduce编程变得非常流行。
2.良好的扩展性
当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
3.高容错性
MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器宕机了,他可以把上面的计算任务转移到另一个节点上运行,不至于这个任务执行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成
4.适合PB级以上海量数据的离线处理
可以实现上千台服务器集群并发工作,提高数据处理能力。
缺点:
- 不擅长实时计算—— MapReduce无法像Mysql一样,在毫秒或者秒级内返回结果。
- 不擅长流式计算—— 流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的
- 不擅长DAG(有向图计算)
基本理论
一、数据本地化的策略
- MapReduce的物理结构:JobTracker(Master)和TaskTracker(slave)
- JobTracker在接受到任务之后会向NameNode发送请求获取文件信息,而JobTracker在收到文件信息之后会对文件进行切片(Split)。切片不是真正将数据切开,而是划分任务量。每一个切片就需要对应一个MapTask
- 实际开发中,Split=Block/n,默认split和block大小一样
- 数据本地化:
- 为了减少跨集群的数据传输,往往会将DataNode和TaskTracker部署在相同的节点上,即一个节点既是DataNode又是TaskTracker
- JobTracker在分配任务的时候,会考虑哪个节点有要处理的数据就将任务分给谁,这样分配的目的是为了能够在处理数据的时候从节点上读取而不需要跨节点读取
- 如果文件为空,那么整个文件作为一个切片来进行处理
- 在MapReduce中,文件分为可切与不可切。如果不指定,则文件默认可切。如果文件不可切,则整个文件作为一个切片来进行处理
- 如果需要调小splitSize,那么需要减小maxSize -FileInputFormat.setMaxInputSplitSize();如果需要增大splitSize,那么需要增大minSize - FileInputFormat.setMinInputSplitSize()
- 在切片的时候要考虑阈值SPLIT_SLOP(1.1)
二、Job/MapReduce执行流程
提交流程
- 检查job指定的输入输出路径
- 计算切片
- 如果需要,为这个job设置分布式缓存的存根账户信息
- 将这个job的jar包和配置信息提交到HFDFS上
- 将job提交到jobtracker,并且选择监控这个job的执行状态
执行流程
- 拆分任务:JobTracker在收到Job任务之后,会将Job任务拆分成MapTask和ReduceTask。MapTask的数量由切片数量来决定,ReduceTask的数量由分区数量来决定
- 分配任务:JobTracker在拆分完成之后会等待TaskTracker的心跳。当JobTracker收到TaskTracker会拆分出来的子任务分配给taskTracker,注意每一个TaskTracker不一定只分配分配一个任务,可以能会被分配到多个任务。在分配的时候,MapTask尽量考虑数据本地化策略(在分配的时候,哪一个TaskTracker上有要处理的数据就将MapTask分配给哪一个TaskTracker,ReduceTask则会考虑分配给相对空闲的TaskTracker
- 下载jar包:TaskTracker领取到任务(MapTask或者ReduceTask)之后,会连接对应的节点来下载jar包。这一步体现的是逻辑移动数据固定的思想
- 执行任务:TaskTracker在下载完jar包之后,会在本节点内部开启一个JVM子进程来执行MapTask或者是ReduceTask。默认情况下,一个JVM子进程只执行一个任务。所以如果一个TaskTracker被分配到了多个任务,那么这个TaskTracker就会多次开启并且关闭JVM子进程
三、uber模式
- uber默认是关闭的,即JVM子进程默认只能执行一个任务;当开启uber模式的时候就可以一个jvm子进程的复用,即开启JVM子进程之后,可以执行多个任务之后再关闭。
Shuffle
一、Map端的Shuffle
- MapTask默认对切片进行按行处理,每处理一行,先将数据写到缓冲区(MemoryBuffer)中
- 每一个MapTask默认自带一个缓冲区,这个缓冲区在内存中,默认占用内存100m的大小
- 当缓冲区的时候达到指定阈值(默认是0.8,当缓冲区的使用达到80%时就会产生溢写操作),会将缓冲区的数据溢写(spill)到磁盘上,产生一个溢写文件(spillable file)
- 溢写后的数据会再次向缓冲区写,如果达到阈值,会再次溢写,每一次溢写,会产生一个新的溢写文件
- 当MapTask将所有的数据处理完成之后会将所有的溢写文件进行合并(merge),合并成一个结果文件(final out),如果maptask处理完之后缓冲区还有数据,会将缓冲区的数据直接合并到fileout中
- 不一定会产生溢写过程,输入的数据量并不能决定溢写次数,而是由MapTask处理逻辑来决定的;溢写文件的大小并不是完全由缓冲区大小和溢写阈值决定,还需要考虑序列化的影响
- 如果没产生溢写,结果都在缓冲区中,那么缓冲区的数据直接冲刷产生一个final out
三、Shuffle的调优
- 扩大缓冲区,实际开发中会将缓冲区设置为250M~400M
- 尽量增加Combiner
- 可以将MapTask的结果进行压缩,
InputFormat --输入格式
- 输入格式类是MapReduce中顶级的输入格式的父类
- 作用:
- 对文件切片
- 针对切片提供输入流用于读取切片
-
InputFormat将HDFS上的数据切片,然后读取切片的内容,将读取的内容传递给Mapper,所以InputFormat读出来的数据是什么格式,mapper接受的就是什么格式
-
如果没有指定,默认使用的是TextInputformat,所以mapper的接受类型也是<longwritable,Text>,->TextInputFormat底层实际上依靠了LineRecordReader来读取数据 - 在TextInputFormat中,从第二个切片开始,每一个MapTask处理的数据都是从当前切片的第二行开始到下一个切片的第一行
-
自定义输入格式:如果MapReduce中提供的输入格式不适合指定场景,那么就需要自己定义输入格式-定义类继承InputFormat,因为实际过程中切片比较复杂所以一般继承FileinputFormat这个类中已经覆盖了切片方法
public class AuthInputFormat extends FileInputFormat<Text, Text> { @Override public RecordReader<Text, Text> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException { return new AuthReader(); } } class AuthReader extends RecordReader<Text, Text> { private LineReader reader; private static byte[] blank = new Text(" ").getBytes(); private Text key; private Text value; private Text tmp; //初始化的方法 //AuthReader在创建的时候会自动调用初始化方法 @Override public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException { //转化为文件切片 FileSplit fsplit = (FileSplit) split; //拿到切片的路径 Path path = fsplit.getPath(); //链接HDFS FileSystem fs = FileSystem.get(URI.create(path.toString()), context.getConfiguration()); //获取输入流 InputStream in = fs.open(path); //输入流是一个字节流,原文件是字符流,三行组成一行数据 //如果使用字节流需要判断什么时候读完三行数据 //将字节流进行包装,最好还能按行读取 reader = new LineReader(in); } //在执行过程中判断是否有下一个间值对需要处理 //如果有,那么就会传递给map中 //如果没有,执行结束 //试着去读取数据,如果读到,那么代表有数据要处理返回true //如果没有读到,那代表没有数据需要处理返回false @Override public boolean nextKeyValue() throws IOException, InterruptedException { key = new Text(); value = new Text(); tmp = new Text(); //会将读取到的一行数据放入传入的参数tmp if (reader.readLine(tmp) == 0) return false; key.set(tmp.toString()); if (reader.readLine(tmp) == 0) return false; value.set(tmp.toString()); value.append(blank, 0, blank.length); if (reader.readLine(tmp) == 0) return false; byte[] bytes = tmp.getBytes(); value.append(bytes, 0, bytes.length); return true; } @Override public Text getCurrentKey() throws IOException, InterruptedException { return key; } @Override public Text getCurrentValue() throws IOException, InterruptedException { return value; } //获取执行进度的方法 @Override public float getProgress() throws IOException, InterruptedException { return 0; } @Override public void close() throws IOException { if (reader != null) { reader.close(); } } }
6. 多源输入:在MapReduce中可以同时指定多个路径来同时处理,这多个路径中的文件格式可以不一样,每一个路径单独制定输入格式以及Mapper类,但是最终使用的一个Reducer
```java
public class MultiDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
System.setProperty("hadoop_user_name","root");
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(MultiDriver.class);
job.setReducerClass(MultiReducer.class);
//设置自定义输入格式
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//同时指定多个输入路径 -多元输入
MultipleInputs.addInputPath(job,new Path("hdfs://192.168.243.134:9000/txt/score.txt"), TextInputFormat.class,MultiMapper1.class);
MultipleInputs.addInputPath(job,new Path("hdfs://192.168.243.134:9000/txt/score3.txt"), AuthInputFormat.class,MultiMapper2.class);
FileOutputFormat.setOutputPath(job,new Path("hdfs://192.168.243.134:9000//result/Multi"));
job.waitForCompletion(true);
}
}
输出格式 OutputFormat
作用
- 校验这个任务的输出路径,例如校验输出路径是否不存在
- 提供RecorderWriter输出流用于写出数据,这个数据是存储在文件系统中
- 如果不指定,默认值的是TextOutputFormat
- OutputFormat是MapReduce中输出格式的顶级父类
数据倾斜
- 每一个任务处理的数据量不均匀,那么就产生了数据倾斜
- 数据本身就具有倾斜特性 - 倾斜度越大效率越低
- Map端倾斜的产生条件:多源输入,文件大小不均等,文件不可切,这三个条件缺一不可,在实际开发过程中Map端的数据倾斜无法处理。
- 实际开发过程中,绝大部分的数据倾斜都产生在Reduce端,而直接导致Reduce端数据倾斜的原因是因为数据的分类(分区),本质原因是因为数据本省不均衡,Reduce端的倾斜可以使用二阶段聚合来解决。
- join:如果需要处理多个文件,且文件之间有关系,可以把小文件先存根,先处理大文件,然后从根中取出处理
小文件
- 危害
- 存储:每一个小文件对应一条元数据,如果HDFS上存储了大量的小文件,那么就会产生大量的元数据。元数据数量过多的时候,导致NameNode的内存被大量占用,还会导致NameNode的查询效率变低。
- 计算:每一个小文件对应一个切片,那么大量的小文件就会产生大量的切片对应了大量的MapTask,当切片过多导致MapTask(线程)的数量,线程数量过多可能会导致服务器的负载压力变大甚至崩溃
- 目前为止处理办法,合并和打包
推测执行机制
- 推测执行机制实际上是Hadoop针对慢任务优化方案,当MapReduce出现慢任务的时候,Hadoop自动将慢任务拷贝一份,谁先处理完就取那个节点的结果,剩下的就会被kill掉
- 出现慢任务的场景:
- 任务分配不均 正常情况下不会出现,Namenode默认会找空闲的节点处理
- 硬件环境不同 正常情况下也不会出现,服务器购买按批买,性能基本一致
- 数据倾斜 集中在这个场景,推测执行机制无效,所以一般是关闭这个机制
- 实际开发中,数据倾斜导致的慢任务的场景更多,此时推测执行机制是无效的,所以一般是会关闭推测执行机制。