Mapreduce
Mapreduce
1.mapreduce是什么
- 分布式离线的计算框架,是一个分布式运算程序的编程框架,是用户开发基于hadoop的数据分析应用的核心框架,mapreduce的核心功能是将用户编写的业务逻辑代码和自带的默认组件整合成一个完整的分布式运算程序,并发运行在一个hadoop集群上。
- hdfs–解决的是海量数据的存储问题。
- mapreduce—解决的是海量数据的计算以及索引问题。
2.mapreduce产生的背景
实现分布式计算框架的思想
- 单个运算程序需要分段
- 阶段一:并行局部运算
- 阶段二:汇总处理(并行处理)
- 不同阶段需要开发不同的程序
- 阶段之间的调度
- 业务程序(task程序)如何分发到集群中并且如何启动
- 如何监控task程序的运行状态,如何处理异常
- 这些所有问题是所有分布式数据分析处理需求中所必须面对的问题,完全可以封装成框架
- MAPREDUCE -->分布式计算框架
- YARN -->分布式资源调度
- NIO,BIO异步非阻塞
模拟实现,并不是真正的实现过程
- 分发运算程序
- worker接受jar包
- worker向客户端发送接收成功的信息,询问如何启动程序
- 发送启动命令给worker
- java -cp maptask 266405.txt 0-128M
- 根据命令启动task任务
- 问题
- 客户端和服务器之间的传输
- 服务器的mr开启服务组件,其中包括SocketServer,这样客户端就可以和服务端进行通信了。
- resourcemanager
- 负责资源调度
- 客户端和服务器之间的传输
mapreduce的架构
1.MR程序的组成
- appMaster---->mapreduce的进程
- maptask —>自己继承Mapper重写的map()方法
- reducetask —>自己继承Reducer重写的reduce()方法
- clinetDriver —>myClient
2.job的提交流程
- 图示
-
流程
- 客户端提交job到RM(resourcemanager)
- RM生成job id 和path,并返回给客户端
- 将资源上传到hdfs之上
- 返回资源已上传成功的消息
- RM会将job放入待调度队列
- RM会先分配一个资源给该job
- 第一次分配资源:启动appMaster
- worker到hdfs上拉取资源包
- worker拉取资源之后,向客户端发送拉取成功的消息,并询问如何启动?
- java -cp appMaster
- 启动appMaster
- 之后的申请资源:启动maptask
- appMaster向RM申请资源,用于执行maptask
- RM分配资源
- NM到hdfs拉取资源之后,启动maptask
- 向appMaster询问,如何启动maptask
- appMaster发送启动命令给worker java -cp maptask a.txt 0-128M
- 启动maptask
- 启动reducetask
- 任意一个maptask任务完成后,appMaster会向RM申请启动reducetask所需的资源。
- 分配资源后,appMaster发送启动命令给worker java -cp reducetask
- reducetask启动
-
只有启动AppMaster的时候会去询问客户端,AppMaster启动之后,整个程序的资源申请都是由appMaster来向resourcemanager申请的
-
所有的任务都在worker中执行,包括appMaster,maptask,reducetask
-
worker:container,相当于jvm。
3.分片
- 图示
-
注意点:
- 最小分片大小 1byte M
- 最大分片大小long的最大值 L
- 分块的大小 B
- 算法
- min(max(M,B),L)不确定
-
如何分片
-
客户端程序要多大的并行度(maptask的数量)
-
map阶段的并行度:
-
可会断会首先先看一下
待处理目录下有多少数据量
-
循环遍历,对每一个文件
-
看看这个文件有多少个block,并将block进行累加
-
返回一个任务规划描述文件:
job.splits
split0 : /date/a.txt 0-128M
split0 : /date/a.txt 128-256M
…
split6 : /date/a.txt 768-800M
…
-
-
分片与分块的区别
-
分片不会真正意义上的把文件切开
只是给一个偏移量。
-
分块是物理上的概念,真正的将数据给切开了
-
分片是逻辑上的概念,没有真正的将数据分开
-
-
能够分配出多少个maptask有哪些因素决定的
- 文件的数量
- 数据量
- 分片的大小
- 分片的大小如果大于块的大小怎么处理???
-
4.mapreduce的关键名词
- job:用户的每一个计算请求称为一个作业
- task:每一个作业,都需要分拆开,并且交由多个主机来完成,拆分出来的指定单位就是任务。
- task分为三种类型:
- Map:负责map阶段的整个数据处理流程
- Reduce:负责整个reduce阶段的数据处理流程
- MrAppMaster:负责整个程序的调度以及状态协调
5.mapreduce的运行流程
- 一个mr程序启动的时候,最先启动的是MrappMaster,MrAppMaster启动后会根据本次job的描述信息,计算出需要的maptask实例数量,然后向集群申请计算资源以启动相应数量的maptask进程
- maptask启动之后,根据给定的数据切片信息进行数据处理,主体流程:
- 利用客户指定的输入inputformat来获取recordreader读取数据,并形成KV键值对
- 将输入的kv对传递给客户自定义的map方法,做逻辑运算,并将map方法输出的结果形成kv键值对并缓存。
- 将缓存的kv键值按照k分区(hashcode%reducetask的数量)排序后不断的溢出写入到磁盘中。
- 会生成一个大文件,其中包括多个分区的数据。并且会维护一个索引文件,当reducetask拉取时,可直接根据偏移量拉取。
- MrAppMaster会监控所有的maptask进程的完成情况,如果其中有maptask任务完成后,会根据客户指定的参数来启动相应数量的reducetask进程,并告知reducetask进程要处理的数据范围(数据分区,对应上方分区)
- reducetask进程启动之后,根据mrAppMaster告知的待处理数据范围从若干个maptask运行所在的机器上获取到若干maptask的输出文件,并在本地进行重新归并排序(归并称为一个文件),按照相同可以的kv对数据为一组 ,然后调用用户自定义的reduce方法,并收集运算输出的结果集kv,然后调用outputformat将最终结果输出到外部存储设备(hdfs)
6.编写mapreduce程序的流程
- 基于mapreduce计算模型编写分布式计算程序,程序员的主要编码工作在与实现map和reduce函数
- 其他并行编程中的种种复杂问题,如分布式存储,工作调度,负载均衡,容错处理,网络通讯等,都是由框架来负责处理。
7.编写mapreduce程序的步骤:
- 用户编写mapreduce程序分为三个部分
- Mapper
- Reducer
- Driver(提交运行mr程序的客户端)启动类
- Mapper的输出数据是kv键值对的形式(kv的数据类型,是可以自定义的)
- Mapper的输出数据是kv键值对的形式(kv的数据类型,是可以自定义的)
- Mapper中的业务逻辑写在map方法中
- map方法对每一个maptask进程传入的kv键值对数据调用一次
- Reduce的输入数据是kv键值对的形式(kv的数据类型,是可以自定义的)
- Reduce的输出数据是kv键值对的形式(kv的数据类型,是可以自定义的)
- Reduce中的业务逻辑写在reduce方法中
- reducetask进程对每一组key相同的kv键值对数据调用一次reduce方法
- 用户自定义的Mapper和Reducer类都要继承自各自的父类
- 整个的程序需要一个Driver来进行提交,提交的是一个描述了各种必要信息的job的对象
8.实例
- 有一批文件(规模达到TB甚至PB级),如何统计这些文件中所有的单词出现的次数。
- Mapper
public class WCMapper extends Mapper<LongWritable , Text , Text , LongWritable>{
private Text k = new Text();
private LongWritable v = new LongWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1.将maptask传递给我们的某一行的文本内容(二进制)转换成String
String line = value.toString();
//2.根据空格将这一行数据切分成单词数组
String[] words = line.split(" ");
//3.循环遍历,将单词按照<word,1>
for (String word:words) {
//4.将单词作为key,将次数1作为value,以便于后续的数据分发,
// 可以根据单词进行分发,以便相同的单词会分发到相同的reducetask中
k.set(word);
context.write(k , v);
}
}
}
- 泛型的解释:Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
- 框架在调用咱们自己写的map业务方法时,会将数据作为参数(一个key,一个value)传递给map方法
- KEYIN:表示是框架(maptask)要传递给我们自己写的map方法的输入参数中key的数据类型
- VALUEIN:表示是框架(maptask)要传递给我们自己写的map方法的输入参数中value的数据类型
- 在默认情况下,框架传入的key是框架从待处理数据中读取到的“某一行”的起始偏移量,所以类型是long框架传入的value是框架从待处理数据中读取到的"某一行"的内容,所以类型是String
- 但是,long或者是String等java的原生数据类型的序列化的效率较低,所以hadoop对其进行了改造,有替代品:LongWritable|Text
- map方法处理完数据之后需要返回结果(一个key,一个value)
- KEYOUT:是map方法处理完后返回结果中key的数据类型
- VALUEOUT:是map方法处理完后返回结果中value的数据类型
- map方法的调用规律
- maptask会对每一行输入数据调用一次map方法
- Reducer
public class WCMapper extends Mapper<LongWritable , Text , Text , LongWritable>{
private Text k = new Text();
private LongWritable v = new LongWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1.将maptask传递给我们的某一行的文本内容(二进制)转换成String
String line = value.toString();
//2.根据空格将这一行数据切分成单词数组
String[] words = line.split(" ");
//3.循环遍历,将单词按照<word,1>
for (String word:words) {
//4.将单词作为key,将次数1作为value,以便于后续的数据分发,
// 可以根据单词进行分发,以便相同的单词会分发到相同的reducetask中
k.set(word);
context.write(k , v);
}
}
}
- 泛型的解释:Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
- reduce方法要接受的参数有两个,一个key,一个vlaue的迭代器:value
- KEYIN:框架(reducetask)要传递给咱们自己写的reduce方法的输入参数的key的数据类型 —>对应map端的输出的key 的类型
- VALUEIN:框架(reducetask)要传递给咱们自己写的reduce方法的输入参数的value的数据类型 —>对应map端输出的value的数据类型
- KEYOUT:reduce处理完成之后的返回结果的key的数据类型
- VALUEOUT:reduce处理完成之后的返回结果的value的数据类型
- reduce方法的调用规律:
- 框架会从map阶段的输出结果中将所有的key相同的kv键值对挑出来组成一组数据,然后调用一次reduce方法
- Driver
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//new Configuration时,会到classpath中去读取默认的配置文件core-default.xml
//然后再读用户自定义的配置文件core-site.cml/hdfs-site.xml,yarn-site.xml,mapred-site.xml
//配置项的优先级
//a.优先级最高的是程序中设置的参数conf.set("fs.defaultFS" , "hdfs:hdp-01:9000");
//b.用户自定义的配置参数
//c.系统默认的配置参数
//1.获取配置对向
Configuration conf = new Configuration();
//2.配置hdfs的服务端的访问接口
conf.set("fs.defaultFS" , "hdfs:hdp-01:9000");
//3.需要获得一个job对象
Job job = Job.getInstance(conf,"wordcount");
//给job指定运行参数
//4.指定本程序的jar的所在的本地路径
job.setJarByClass(WordCount.class);
//5.指定本job要使用那个Mapper业务类
job.setMapperClass(WCMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
//6.指定本job要执行那个Reducer类
job.setReducerClass(MyReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//7.指定job的输入输出文件目录
FileInputFormat.setInputPaths(job , new Path(args[0]));
FileOutputFormat.setOutputPath(job , new Path(args[1]));
//8.提交job
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
//hadoop jar wc.jar WordCount
}