MapReduce增强(下)


MapTask运行机制详解以及MapTask的并行度

  • MapTask运行流程

    第一步:读取数据组件InputFormat(默认TextInputFormat)会通过getSplits方法,对输入目录中的文件(输入目录也就是TextInputFormat的Path)进行逻辑切片得到splits

        ps. getSplits方法属于FileInputFormat,该方法返回的就是一个文件有多少个切片,一个切片对应一个maptask的任务。

           切片大小如何决定的?

           如果一个切片256M,那么一个512M的文件会产生两个切片,两个maptask

           如果一个切片是128M,那么一个512M的文件产生四个切片,产生四个maptaks

    第二步:将输入文件切分为splits之后,由RecordReader对象(默认LineRecordReader)进行读取,以 \n 作为分隔符,读取一行数据,返回 <key1,value1>(Key1表示每行首字符偏移值,value1表示这一行文本内容)

    第三步:返回<key1,value1>后,进入用户自己继承的Mapper类中,执行用户重写的map函数。(RecordReader读取一行这里调用一次)

    第四步:map逻辑完之后,将map的每条结果通过context.write进行collect数据收集。在collect中,会先对其进行分区处理,默认使用HashPartitioner(分区数,也就是Partitioner用户可以根据自己的需要自定义,设置完成后需要在main方法中添加job.setNumReduceTask() 设置reduceTask数,并且自定义分区后无法在本地系统运行代码,必须打包上传到HDFS上运行)

    第五步:接下来,会将数据写入内存,内存中这片区域叫做环形缓冲区,缓冲区的作用是批量收集map结果减少磁盘IO的影响<key,value> 对以及 Partition 的结果都会被写入缓冲区。当然写入之前,key与value值都会被序列化成字节数组。

        环形缓冲区其实是一个数组,数组中存放着key、value的序列化数据key、value的元数据信息,包括partitionkey的起始位置value的起始位置以及value的长度

        缓冲区是有大小限制,默认是 100MB。MapTask输出结果过多可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,这种操作被称为Spill,中文可译为溢写(这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程)。整个缓冲区有个溢写的比例 spill.percent(这个比例默认是0.8),不会阻止map的结果输出。当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这80MB的内存,执行溢写过程。Map task的输出结果还可以往剩下的20MB内存中写,互不影响。

    环形缓冲区的大小,涉及到MapTask的调优过程。如果内存充足,可以将环形缓冲区的大小调大。

    第六步:当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。

        如果设置了Combiner,会在排序后进行,Combiner适用于Reduce的输入<key,value>对与输出<key,value>对类型完全一致,且不影响最终结果的场景,比如累加最大值等。Combiner会将有相同key的<key,value>对的value加起来,减少溢写到磁盘的数据量

    第七步合并溢写文件。每次溢写会在磁盘上生成一个临时文件(写之前判断是否有combiner),如果map的输出结果很大,就会有多次这样的溢写发生,磁盘上相应的也就会有多个临时文件存在。当整个数据处理结束之后开始对磁盘中的临时文件进行merge合并,因为最终的文件只有一个,写入磁盘,并且为这个文件提供了一个索引文件,以记录每个reduce对应数据的偏移量。

  • MapTask的并行度

    MapTask的并行度,就是指代有多少个MapTask

  • MapTask的一些基础设置配置

  1. 设置环型缓冲区的内存值大小
  2. mapreduce.task.io.sort.mb 100 -> 默认设置100M
  3. 设置溢写百分比
  4. mapreduce.map.sort.spill.percent 0.80 -> 默认设置80%
  5. 设置溢写数据目录
  6. mapreduce.cluster.local.dir ${hadoop.tmp.dir}/mapred/local -> 默认设置
  7. 设置一次最多合并多少个溢写文件
  8. mapreduce.task.io.sort.factor 10 -> 默认一次最多合并10个溢写文件

ReduceTask运行机制以及ReduceTask的并行度

  • ReduceTask运行流程

    第一步Copy阶段。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求maptask获取属于自己的文件。

    第二步Merge阶段。和Map端的Merge相同,只是存放的数值是不同Map端Copy过来的。

        Copy过来的数据会先放入内存缓冲区中(这里的缓冲区大小要比map端的更为灵活)。merge有三种形式:内存到内存内存到磁盘磁盘到磁盘(默认情况下第一种形式不启用)。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程(这个过程中如果你设置有Combiner,也是会启用的),然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的文件。

    第三步合并排序。把分散的数据合并成一个大的数据后,还会再对合并后的数据排序。

    第四步对排序后的键值对调用reduce方法。key相等的键值<key,value>对调用一次reduce方法,每次调用会产生零个或者多个键值对,最后把这些输出的键值对写入到HDFS文件中。

  • ReduceTask运行流程图


MapReduce的shuffle过程

  • 概述

    shuffle就是指map阶段处理的数据如何传递给reduce阶段(就是分区、排序、规约和分组的操作),是MapReduce框架中最关键的一个流程

  • 步骤

    第一步Collect阶段。将MapTask的结果输出到默认大小为100M的环形缓冲区,保存的是 <key,value>Partition分区信息等。

    第二步Spill阶段。当内存中的数据量达到一定的阈值的时候,就会将数据写入本地磁盘,在将数据写入磁盘之前需要对数据进行一次排序的操作,如果配置了combiner,还会将有相同分区号和key的数据进行排序。

    第三步Merge阶段。把所有溢出的临时文件进行一次合并操作,以确保一个MapTask最终只产生一个中间数据文件。

    第四步Copy阶段。ReduceTask启动Fetcher线程到已经完成MapTask的节点上复制一份属于自己的数据,这些数据默认会保存在内存的缓冲区中,当内存的缓冲区达到一定的阀值的时候,就会将数据写到磁盘之上。

    第五步Merge阶段。在ReduceTask远程复制数据的同时,会在后台开启两个线程对内存到本地的数据文件进行合并操作。

    第六步Sort阶段。在对数据进行合并的同时,会进行排序操作,由于MapTask阶段已经对数据进行了局部的排序,ReduceTask只需保证Copy的数据的最终整体有效性即可。


shuffle阶段数据的压缩机制

  • 概述

    从map阶段输出的数据,都要通过网络拷贝,发送到reduce阶段,这一过程中,涉及到大量的网络IO,如果数据能够进行压缩,那么数据的发送量就会少得多。

  • 最好用的Hadoop压缩算法

    Snappy! Snappy! Snappy!

  • 在代码中设置压缩

  1. //设置map阶段的压缩
  2. Configuration configuration = new Configuration();
  3. configuration.set("mapreduce.map.output.compress","true");
  4. configuration.set("mapreduce.map.output.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");
  1. //设置reduce阶段的压缩
  2. configuration.set("mapreduce.output.fileoutputformat.compress","true");
  3. configuration.set("mapreduce.output.fileoutputformat.compress.type","RECORD");
  4. configuration.set("mapreduce.output.fileoutputformat.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");
  • 配置全局的MapReduce压缩
  1. cd /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop
  2. vim mapred-site.xml
  1. <!--map端输出数据进行压缩-->
  2. <property>
  3. <name>mapreduce.map.output.compress</name>
  4. <value>true</value>
  5. </property>
  6. <property>
  7. <name>mapreduce.map.output.compress.codec</name>
  8. <value>org.apache.hadoop.io.compress.SnappyCodec</value>
  9. </property>
  1. <!--reduce端输出数据进行压缩-->
  2. <property>
  3. <name>mapreduce.output.fileoutputformat.compress</name>
  4. <value>true</value>
  5. </property>
  6. <property>
  7. <name>mapreduce.output.fileoutputformat.compress.type</name>
  8. <value>RECORD</value>
  9. </property>
  10. <property>
  11. <name>mapreduce.output.fileoutputformat.compress.codec</name>
  12. <value>org.apache.hadoop.io.compress.SnappyCodec</value>
  13. </property>

MapReduce编程案例

  • reduce端join算法实现

定义ReduceJoin的Mapper类

  1. package cn.itcast.mr.demo5;
  2. import org.apache.hadoop.fs.Path;
  3. import org.apache.hadoop.io.LongWritable;
  4. import org.apache.hadoop.io.Text;
  5. import org.apache.hadoop.mapreduce.Mapper;
  6. import org.apache.hadoop.mapreduce.lib.input.FileSplit;
  7. import java.io.IOException;
  8. public class ReduceJoinMapper extends Mapper<LongWritable, Text, Text, Text> {
  9. //创建Text对象
  10. Text k2 = new Text();
  11. @Override
  12. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  13. //如何判断文件究竟是从哪个文件里面来的
  14. //获取文件的切片
  15. FileSplit inputSplit = (FileSplit) context.getInputSplit();
  16. //获取到了文件存放的路径
  17. Path path = inputSplit.getPath();
  18. //获取文件的名称
  19. String name = path.getName();
  20. System.out.println(name);
  21. //可以使用文件名来判断我们的数据是来自哪个文件
  22. String line = value.toString();
  23. if (line.startsWith("p")) {
  24. //商品表中的数据
  25. String[] split = line.split(",");
  26. k2.set(split[0]);
  27. context.write(k2, value);
  28. } else {
  29. String[] split = line.split(",");
  30. k2.set(split[2]);
  31. context.write(k2, value);
  32. }
  33. }
  34. }

定义ReduceJoin的Reducer类

  1. package cn.itcast.mr.demo5;
  2. import org.apache.hadoop.io.NullWritable;
  3. import org.apache.hadoop.io.Text;
  4. import org.apache.hadoop.mapreduce.Reducer;
  5. import java.io.IOException;
  6. public class ReduceJoinReducer extends Reducer<Text, Text, Text, NullWritable> {
  7. @Override
  8. protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
  9. //定义订单和商品内容
  10. String orderLine = "";
  11. String productLine = "";
  12. for (Text value : values) {
  13. //如果value输出首字母为"p",则为商品的内容,否则为订单内容
  14. if (value.toString().startsWith("p")) {
  15. productLine = value.toString();
  16. } else {
  17. orderLine = value.toString();
  18. }
  19. }
  20. context.write(new Text(orderLine + "\t" + productLine), NullWritable.get());
  21. }
  22. }

程序main函数入口

  1. package cn.itcast.mr.demo5;
  2. import org.apache.hadoop.conf.Configuration;
  3. import org.apache.hadoop.conf.Configured;
  4. import org.apache.hadoop.fs.Path;
  5. import org.apache.hadoop.io.NullWritable;
  6. import org.apache.hadoop.io.Text;
  7. import org.apache.hadoop.mapreduce.Job;
  8. import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
  9. import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
  10. import org.apache.hadoop.util.Tool;
  11. import org.apache.hadoop.util.ToolRunner;
  12. public class ReduceJoinMain extends Configured implements Tool {
  13. @Override
  14. public int run(String[] args) throws Exception {
  15. //创建job对象
  16. Job job = Job.getInstance(super.getConf(), "ReduceJoin");
  17. //获取输入数据,设置输入路径
  18. job.setInputFormatClass(TextInputFormat.class);
  19. TextInputFormat.setInputPaths(job, new Path("file:////Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/input"));
  20. //map逻辑自定义
  21. job.setMapperClass(ReduceJoinMapper.class);
  22. //设置k2,v2类型
  23. job.setMapOutputKeyClass(Text.class);
  24. job.setMapOutputValueClass(Text.class);
  25. //reduce逻辑自定义
  26. job.setReducerClass(ReduceJoinReducer.class);
  27. //设置k3,v3类型
  28. job.setOutputKeyClass(Text.class);
  29. job.setOutputValueClass(NullWritable.class);
  30. //输出数据,设置输出路径
  31. job.setOutputFormatClass(TextOutputFormat.class);
  32. TextOutputFormat.setOutputPath(job, new Path("file:////Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/reduceJoin_output"));
  33. //提交任务至集群
  34. boolean b = job.waitForCompletion(true);
  35. return b ? 0 : 1;
  36. }
  37. public static void main(String[] args) throws Exception {
  38. //使用ToolRunner工具获取退出状态码
  39. int run = ToolRunner.run(new Configuration(), new ReduceJoinMain(), args);
  40. //系统退出
  41. System.exit(run);
  42. }
  43. }

缺点:这种方式中,join的操作是在reduce阶段完成,reduce端的处理压力太大,map节点的运算负载则很低,资源利用率不高,且在reduce阶段极易产生数据倾斜

解决办法:map端join实现方式

  • map端join算法实现

先在HDFS创建一个文件夹,并在linux虚拟机上将产品表的数据上传到新创建的文件夹中

  1. cd /export/servers
  2. hdfs dfs -mkdir -p /cacheFile
  3. hdfs dfs -put pdts.txt /cacheFile

定义mapJoin的Mapper类

  1. package cn.itcast.mr.demo6;
  2. import org.apache.hadoop.conf.Configuration;
  3. import org.apache.hadoop.filecache.DistributedCache;
  4. import org.apache.hadoop.fs.FSDataInputStream;
  5. import org.apache.hadoop.fs.FileSystem;
  6. import org.apache.hadoop.fs.Path;
  7. import org.apache.hadoop.io.LongWritable;
  8. import org.apache.hadoop.io.NullWritable;
  9. import org.apache.hadoop.io.Text;
  10. import org.apache.hadoop.mapreduce.Mapper;
  11. import java.io.BufferedReader;
  12. import java.io.IOException;
  13. import java.io.InputStreamReader;
  14. import java.net.URI;
  15. import java.util.HashMap;
  16. import java.util.Map;
  17. public class MapJoinMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
  18. //在程序main函数方法里添加了缓存文件,这里就可以获取到缓存文件
  19. //定义map
  20. Map<String, String> map = null;
  21. /*
  22. 重写setup方法,获取缓存文件,然后将缓存文件的内容存储到map当中去
  23. */
  24. @Override
  25. protected void setup(Context context) throws IOException, InterruptedException {
  26. map = new HashMap<String, String>();
  27. //从context中获取configuration
  28. Configuration configuration = context.getConfiguration();
  29. //我们只有一个缓存文件
  30. URI[] cacheFiles = DistributedCache.getCacheFiles(configuration);
  31. //获取缓存文件的URI,拿到URI就可以访问文件
  32. URI cacheFile = cacheFiles[0];
  33. //获取文件系统
  34. FileSystem fileSystem = FileSystem.get(cacheFile, configuration);
  35. //获取到文件的输入流
  36. FSDataInputStream open = fileSystem.open(new Path(cacheFile));
  37. InputStreamReader inputStreamReader = new InputStreamReader(open);
  38. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  39. //将输入流转换读取成字符串
  40. String line = null;
  41. while ((line = bufferedReader.readLine()) != null) {
  42. String[] lineArray = line.split(",");
  43. map.put(lineArray[0], line);
  44. }
  45. }
  46. @Override
  47. protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
  48. String[] split = value.toString().split(",");
  49. //获取商品表的数据
  50. String product = map.get(split[2]);
  51. //将商品表和订单表的数据进行拼接,然后输出
  52. context.write(new Text(value.toString() + "\t" + product), NullWritable.get());
  53. }
  54. }

程序main函数的入口

  1. package cn.itcast.mr.demo6;
  2. import org.apache.hadoop.conf.Configuration;
  3. import org.apache.hadoop.conf.Configured;
  4. import org.apache.hadoop.filecache.DistributedCache;
  5. import org.apache.hadoop.fs.Path;
  6. import org.apache.hadoop.io.NullWritable;
  7. import org.apache.hadoop.io.Text;
  8. import org.apache.hadoop.mapreduce.Job;
  9. import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
  10. import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
  11. import org.apache.hadoop.util.Tool;
  12. import org.apache.hadoop.util.ToolRunner;
  13. import java.net.URI;
  14. public class MapJoinMain extends Configured implements Tool {
  15. @Override
  16. public int run(String[] args) throws Exception {
  17. //添加缓存文件
  18. DistributedCache.addCacheFile(new URI("hdfs://node01:8020/cacheFile/pdts.txt"), super.getConf());
  19. //获取Job对象
  20. Job job = Job.getInstance(super.getConf(), "mapJoin");
  21. //获取输入数据,设置输入路径
  22. job.setInputFormatClass(TextInputFormat.class);
  23. TextInputFormat.setInputPaths(job, new Path("file:////Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/map_join_iput/orders.txt"));
  24. //自定义map逻辑
  25. job.setMapperClass(MapJoinMapper.class);
  26. //设置k2,v2类型
  27. job.setMapOutputKeyClass(Text.class);
  28. job.setMapOutputValueClass(NullWritable.class);
  29. //输出数据,设置输出路径
  30. job.setOutputFormatClass(TextOutputFormat.class);
  31. TextOutputFormat.setOutputPath(job, new Path("/Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/map_join_output"));
  32. //提交任务到集群
  33. boolean b = job.waitForCompletion(true);
  34. return b ? 0 : 1;
  35. }
  36. public static void main(String[] args) throws Exception {
  37. //使用toolRunner工具获取退出状态码
  38. int run = ToolRunner.run(new Configuration(), new MapJoinMain(), args);
  39. //系统退出
  40. System.exit(run);
  41. }
  42. }

【Hadoop离线基础总结】MapReduce增强(下)的更多相关文章

  1. 【Hadoop离线基础总结】oozie的安装部署与使用

    目录 简单介绍 概述 架构 安装部署 1.修改core-site.xml 2.上传oozie的安装包并解压 3.解压hadooplibs到与oozie平行的目录 4.创建libext目录,并拷贝依赖包 ...

  2. 【Hadoop离线基础总结】impala简单介绍及安装部署

    目录 impala的简单介绍 概述 优点 缺点 impala和Hive的关系 impala如何和CDH一起工作 impala的架构及查询计划 impala/hive/spark 对比 impala的安 ...

  3. 【Hadoop离线基础总结】Hive调优手段

    Hive调优手段 最常用的调优手段 Fetch抓取 MapJoin 分区裁剪 列裁剪 控制map个数以及reduce个数 JVM重用 数据压缩 Fetch的抓取 出现原因 Hive中对某些情况的查询不 ...

  4. 【Hadoop离线基础总结】Hue的简单介绍和安装部署

    目录 Hue的简单介绍 概述 核心功能 安装部署 下载Hue的压缩包并上传到linux解压 编译安装启动 启动Hue进程 hue与其他框架的集成 Hue与Hadoop集成 Hue与Hive集成 Hue ...

  5. 【Hadoop离线基础总结】流量日志分析网站整体架构模块开发

    目录 数据仓库设计 维度建模概述 维度建模的三种模式 本项目中数据仓库的设计 ETL开发 创建ODS层数据表 导入ODS层数据 生成ODS层明细宽表 统计分析开发 流量分析 受访分析 访客visit分 ...

  6. 【Hadoop离线基础总结】Sqoop常用命令及参数

    目录 常用命令 常用公用参数 公用参数:数据库连接 公用参数:import 公用参数:export 公用参数:hive 常用命令&参数 从关系表导入--import 导出到关系表--expor ...

  7. 【Hadoop离线基础总结】MapReduce增强(上)

    MapReduce增强 MapReduce的分区与reduceTask的数量 概述 MapReduce当中的分区:物以类聚,人以群分.相同key的数据,去往同一个reduce. ReduceTask的 ...

  8. 【Hadoop离线基础总结】工作流调度器azkaban

    目录 Azkaban概述 工作流调度系统的作用 工作流调度系统的实现 常见工作流调度工具对比 Azkaban简单介绍 安装部署 Azkaban的编译 azkaban单服务模式安装与使用 azkaban ...

  9. 【Hadoop离线基础总结】linux基础增强

    linux基础增强 查找命令 grep命令  (print lines matching a pattern) 概述: grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打 ...

随机推荐

  1. jquary 动画j

    1) 点击 id为d1的正方体,将其后所有class为div1的正方体背景色设置为绿色. 代码如下:       <div class="div1" > </di ...

  2. 用pytorch做手写数字识别,识别l率达97.8%

    pytorch做手写数字识别 效果如下: 工程目录如下 第一步  数据获取 下载MNIST库,这个库在网上,执行下面代码自动下载到当前data文件夹下 from torchvision.dataset ...

  3. Java 多线程 -- 指令重排(HappenBefore)

    指令重排是指:代码执行顺序和预期不一致. 代码运行一般步骤为: 1.从内存中获取指令解码 2.计算值 3.执行代码操作 4.把结果写回内存 而写回内存的操作比较耗时,CPU为了性能,可能不会等它完成, ...

  4. JasperReports入门教程(一):快速入门

    JasperReports入门教程(一):快速入门 背景 现在公司的项目需要实现一个可以配置的报表,以便快速的适应客户的需求变化.后来在网上查资料发现可以使用JasperReports + Jaspe ...

  5. 关于go的通信通道channel——chan的一些问题

    go版本 1.8 chan类型的声明,有以下几种: var c chan int c := make(chan int) //slice.map.chan都可以通过用make来初始化,其中map.ch ...

  6. 从头学pytorch(十三):使用GPU做计算

    GPU计算 默认情况下,pytorch将数据保存在内存,而不是显存. 查看显卡信息 nvidia-smi 我的机器输出如下: Fri Jan 3 16:20:51 2020 +------------ ...

  7. swoole学习--登录模块

    使用swoole+thinkphp6.0+redis 结合开发的登录模块,做完之后有几点感悟: 1.不要相信任务数据,包括请求的外部接口,特别是超时者部分,尽可能的交给task完成. 2.原来可以在入 ...

  8. think--数据库的设置

    1.在项目下的Common下的Conf下的config.php 配置: 'DB_TYPE' => 'mysql', // 数据库类型 'DB_HOST' => 'localhost', / ...

  9. TP5 JSON对象数组转换为普通数组

    来源于:https://blog.csdn.net/lingchen__/article/details/67671047 使用TP5框架做项目时,对于数据的查询返回的都是对象,虽然也可以当做普通的数 ...

  10. How to use QueryPerformanceCounter? (c++,不使用 .Net)

    出处:https://stackoverflow.com/questions/1739259/how-to-use-queryperformancecounter 参考:https://docs.mi ...