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的一些基础设置配置

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

  • 在代码中设置压缩

//设置map阶段的压缩
Configuration configuration = new Configuration();
configuration.set("mapreduce.map.output.compress","true");
configuration.set("mapreduce.map.output.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");
//设置reduce阶段的压缩
configuration.set("mapreduce.output.fileoutputformat.compress","true");
configuration.set("mapreduce.output.fileoutputformat.compress.type","RECORD");
configuration.set("mapreduce.output.fileoutputformat.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");
  • 配置全局的MapReduce压缩
cd /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop

vim mapred-site.xml
<!--map端输出数据进行压缩-->
<property>
<name>mapreduce.map.output.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.map.output.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>
<!--reduce端输出数据进行压缩-->
<property>
<name>mapreduce.output.fileoutputformat.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.output.fileoutputformat.compress.type</name>
<value>RECORD</value>
</property>
<property>
<name>mapreduce.output.fileoutputformat.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>

MapReduce编程案例

  • reduce端join算法实现

定义ReduceJoin的Mapper类

package cn.itcast.mr.demo5;

import org.apache.hadoop.fs.Path;
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; public class ReduceJoinMapper extends Mapper<LongWritable, Text, Text, Text> { //创建Text对象
Text k2 = new Text(); @Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//如何判断文件究竟是从哪个文件里面来的
//获取文件的切片
FileSplit inputSplit = (FileSplit) context.getInputSplit();
//获取到了文件存放的路径
Path path = inputSplit.getPath();
//获取文件的名称
String name = path.getName();
System.out.println(name);
//可以使用文件名来判断我们的数据是来自哪个文件 String line = value.toString();
if (line.startsWith("p")) {
//商品表中的数据
String[] split = line.split(",");
k2.set(split[0]);
context.write(k2, value);
} else {
String[] split = line.split(",");
k2.set(split[2]);
context.write(k2, value);
}
}
}

定义ReduceJoin的Reducer类

package cn.itcast.mr.demo5;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; public class ReduceJoinReducer extends Reducer<Text, Text, Text, NullWritable> {
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//定义订单和商品内容
String orderLine = "";
String productLine = "";
for (Text value : values) {
//如果value输出首字母为"p",则为商品的内容,否则为订单内容
if (value.toString().startsWith("p")) {
productLine = value.toString();
} else {
orderLine = value.toString();
}
}
context.write(new Text(orderLine + "\t" + productLine), NullWritable.get());
}
}

程序main函数入口

package cn.itcast.mr.demo5;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner; public class ReduceJoinMain extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
//创建job对象
Job job = Job.getInstance(super.getConf(), "ReduceJoin");
//获取输入数据,设置输入路径
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.setInputPaths(job, new Path("file:////Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/input")); //map逻辑自定义
job.setMapperClass(ReduceJoinMapper.class);
//设置k2,v2类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class); //reduce逻辑自定义
job.setReducerClass(ReduceJoinReducer.class);
//设置k3,v3类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class); //输出数据,设置输出路径
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job, new Path("file:////Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/reduceJoin_output")); //提交任务至集群
boolean b = job.waitForCompletion(true);
return b ? 0 : 1;
} public static void main(String[] args) throws Exception {
//使用ToolRunner工具获取退出状态码
int run = ToolRunner.run(new Configuration(), new ReduceJoinMain(), args);
//系统退出
System.exit(run);
}
}

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

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

  • map端join算法实现

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

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

定义mapJoin的Mapper类

package cn.itcast.mr.demo6;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map; public class MapJoinMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
//在程序main函数方法里添加了缓存文件,这里就可以获取到缓存文件
//定义map
Map<String, String> map = null; /*
重写setup方法,获取缓存文件,然后将缓存文件的内容存储到map当中去
*/
@Override
protected void setup(Context context) throws IOException, InterruptedException {
map = new HashMap<String, String>();
//从context中获取configuration
Configuration configuration = context.getConfiguration();
//我们只有一个缓存文件
URI[] cacheFiles = DistributedCache.getCacheFiles(configuration);
//获取缓存文件的URI,拿到URI就可以访问文件
URI cacheFile = cacheFiles[0];
//获取文件系统
FileSystem fileSystem = FileSystem.get(cacheFile, configuration);
//获取到文件的输入流
FSDataInputStream open = fileSystem.open(new Path(cacheFile));
InputStreamReader inputStreamReader = new InputStreamReader(open);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//将输入流转换读取成字符串
String line = null;
while ((line = bufferedReader.readLine()) != null) {
String[] lineArray = line.split(",");
map.put(lineArray[0], line);
}
} @Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split(",");
//获取商品表的数据
String product = map.get(split[2]);
//将商品表和订单表的数据进行拼接,然后输出
context.write(new Text(value.toString() + "\t" + product), NullWritable.get());
}
}

程序main函数的入口

package cn.itcast.mr.demo6;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner; import java.net.URI; public class MapJoinMain extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
//添加缓存文件
DistributedCache.addCacheFile(new URI("hdfs://node01:8020/cacheFile/pdts.txt"), super.getConf()); //获取Job对象
Job job = Job.getInstance(super.getConf(), "mapJoin");
//获取输入数据,设置输入路径
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.setInputPaths(job, new Path("file:////Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/map_join_iput/orders.txt")); //自定义map逻辑
job.setMapperClass(MapJoinMapper.class);
//设置k2,v2类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class); //输出数据,设置输出路径
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job, new Path("/Volumes/赵壮备份/大数据离线课程资料/4.大数据离线第四天/map端join/map_join_output")); //提交任务到集群
boolean b = job.waitForCompletion(true);
return b ? 0 : 1;
} public static void main(String[] args) throws Exception {
//使用toolRunner工具获取退出状态码
int run = ToolRunner.run(new Configuration(), new MapJoinMain(), args);
//系统退出
System.exit(run);
}
}

【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. Python 输出漂亮的表格的5个案例,实用方便

    文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:程序IT圈 PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行 ...

  2. CISCN love_math和roarctf的easy_clac学习分析

    Love_math 题目源码: <?php error_reporting(0); //听说你很喜欢数学,不知道你是否爱它胜过爱flag if(!isset($_GET['c'])){ show ...

  3. 测试Thread中的常用方法

    package com.yhqtv.java; /* *测试Thread中的常用方法: * 1.start():启动当前线程:调用当前线程的run() * 2.run():通常需要重写Thread类的 ...

  4. B站百大UP主党妹被黑客勒索!!!

    4月27日,哔哩哔哩视频网站的UP主“机智的党妹”发布消息称,自己被黑客勒索了.她的视频表示:“事发突然,我被勒索了,你也有可能继续被诈骗!这种诈骗的页面是由病毒程序自动生成并留在那里的.”根据她的介 ...

  5. sqlliab7-8

    less-7 https://www.jianshu.com/p/20d1282e6e1d ?id=0')) union select 1,'2','<?php @eval($_POST[&qu ...

  6. PHP使用token防止表单重复提交的方法

    本文实例讲述了PHP使用token防止表单重复提交的方法.分享给大家供大家参考,具体如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2 ...

  7. 去掉input阴影&隐藏滚动条&抛异常&预加载&curl传json

    1.隐藏滚动条:-webkit-scrollbar{ display:none; } 2.array_walk():数组里的每个元素执行一个自定义函数: array_map():数组里的每个元素执行一 ...

  8. 用libevent写的海康摄像头rtsp客户端

    之前一直使用live555作为RTSP的客户端,但其框架臃肿,虽然支持各种格式,但实际中并没有这些需求,关键是其注重于格式的解析,却不注重网络IO,单线程下性能也不高,重新用libevent编写rts ...

  9. ps 和 top

    ps 进程和线程的关系: (1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程. (2)资源分配给进程,同一进程的所有线程共享该进程的所有资源. (3)处理机分给线程,即真正在处 ...

  10. Nginx重写请求后将url?后的参数去除

    2019独角兽企业重金招聘Python工程师标准>>> 使用?结尾     注意,关键点就在于"?"这个尾缀.重定向的目标地址结尾处如果加了?号,则不会再转发传递 ...