1. 对MapReduce的理解
是什么:Hadoop默认自带的分布式计算框架
做什么:提供一系列接口(核心类:InputFormat、OutputFormat、Mapper、Reducer、Driver),让用户能够实现自定义业务功能的分布式计算任务
【优点】:
- 高扩展性:计算资源不够,直接增加节点数量即可。质量可能不够,数量一定管够
- 高容错性:一个节点任务失败,能自动转移到其他空闲节点
- 适合大数据处理:得益于其扩展性,只要数量足够,能够计算TB级别的数据
【缺点】:
- 无法进行实时计算:太慢了!!!太慢了!!!
- 不擅长流式计算:MR的输入数据一般都是静态的,无法处理动态的输入格式
- 不擅长迭代计算:一个完整的MR计算过程一般为Mapper - Reducer - 写出结果,频繁的迭代计算将要启动多组MR并串联,而每次MR的结果都是会以文件的形式写出,给下一个MR组输入的话又得重新读取,太过繁琐
【个人总结】:
个人认为,MR程序的缺点总结的话就是一点,IO过程太多了。首先map阶段输入需要从HDFS中读取数据(这个过程已经是比较慢的了),然后map阶段业务完成后写出数据(一次IO),而reduce阶段的输入首先得从map节点读取已经写出的结果文件(可能是网络IO),读到reduce节点后如果结果集太大又会写到磁盘(又一次IO),最后才是主要的reduce过程,最后结果仍然是写回磁盘......一组MR的执行,可能要涉及到同节点、异节点的多次IO,如果用来做机器学习之类的复杂迭代计算,可能IO时间比核心业务时间更长,因此MR适合做一些一次性、单步骤、大量级的计算。
2. Mapper & Reducer编程需要注意的点
- map() 方法是对每个
键值对调用一次 - reduce() 方法是对每个key调用一次,每个key可能对应多个value,其中value封装为迭代器对象
- 不要导错包!!!不要导错包!!!不要导错包!!! 血泪的踩坑之旅 -- mapred(老版本,不用) -- mapreduce(用它!)
3. MR的切片
【什么是切片?】
MR要进行计算,就必须要有输入,而MR是分布式计算模式,因此对于数据量较大的计算,就会启动多个Mapper和Reducer(Reducer数目一般都会远小于Mapper),而作为第一道环节,多个Mapper都接受完整的数据集,那分布式完全就没有意义了,因此在数据进入Mapper前,会首先进行逻辑切分,将整个数据集分成多份,每份送给一个Mapper进行处理。因此,切片数 = Mapper(MapTask) 的个数
【怎么切?】
第一种:根据文件大小切(默认切片方式) -- 附源码解析
- 根据参数得到SplitSize,即满足多大了就切一块,见源码解析
- 文件优先级 > 切片,即如果一个文件不够SplitSize,则直接作为一个切片,不是把所有文件当整体
- 一定要明确一点:这里的切片只是逻辑切片!!相当于隔一段给文件"打一个标记",并不是真正物理上将数据文件划分为几份。实际上Client端做的分片仅仅是提交job.split信息,即将这些标记发送给MR,Map阶段每个Mapper将会根据这些标记去读取各自被分配的那部分数据
protected long getFormatMinSplitSize() {
return 1L;
}
// 获取最小切片大小 -- 默认为 1
public static long getMinSplitSize(JobContext job) {
return job.getConfiguration().getLong("mapreduce.input.fileinputformat.split.minsize", 1L);
}
// 获取最大切片大小 -- 默认为Long.MAX
public static long getMaxSplitSize(JobContext context) {
return context.getConfiguration().getLong("mapreduce.input.fileinputformat.split.maxsize", 9223372036854775807L);
}
// ......(此处省略一万行代码)
if (this.isSplitable(job, path)) {
long blockSize = file.getBlockSize();
// 实际切片的大小 -- computeSplitSize函数见下
long splitSize = this.computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining;
int blkIndex;
// 如果剩下的文件大于 1.1 * SplitSize,那么继续切分;否则直接作为一个分片; 防止出现过小文件
for(bytesRemaining = length; (double)bytesRemaining / (double)splitSize > 1.1D; bytesRemaining -= splitSize) {
blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(this.makeSplit(path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts()));
}
// ......(此处省略一万行代码)
// 核心公式
protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
// 默认情况下就是块文件的大小 -- 128M
return Math.max(minSize, Math.min(maxSize, blockSize));
}
第二种:根据文件行数切
- 相当于将评判标准从文件大小换成了行数,设置多少行为一个切片
- 同样不会跨文件切
第三种:消灭小文件切法
当有大量小文件时,如果用上面两种机制,则会导致切片很多,启用非常多的Mapper,资源利用率极低,因此设置了一种切片方式专门用于小文件过多的场景
重要参数: CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 默认4m
- Step1:虚拟存储,机制见伪代码
if (文件A.size() <= MaxInputSplitSize){
A文件不做处理,直接为一个虚拟文件;
}else if(文件A.size() <= MaxInputSplitSize * 2){
A平均切分为两个等大小的虚拟文件[A1,A2];
}else{
A切掉大小为MaxInputSplitSize的一块,剩下的部分继续判断;
}
- Step2:重新切片
if(文件B1 >= MaxInputSplitSize){
B1作为一个切片;
}else{
B1等待和下一个虚拟文件合并;
if(合并后的大小 >= MaxInputSplitSize){
合并文件成为一个切片;
}else{
继续等待合并;
}
}
4. MR常用的输入类型
全部都是继承了FileInputFormat抽象类,如果自定义输入类型,需要继承该抽象类并重写RecordReader方法(实现具体自定义读的逻辑)
InputFormat | Split_Type | Key -- Type | Value -- Type |
---|---|---|---|
TextInputFormat | 按文件大小切(默认) | 每行偏移量offset |
该行文本 |
NLineInputFormat | 按固定行数切 | 每行偏移量offset |
该行文本 |
CombineTextInputFormat | 消灭小文件切法 | 每行偏移量offset |
该行文本 |
KeyValueInputFormat | 按文件大小切(默认) | 每行Split后的field[0] |
field[0] |
这四种主要的输入类型都是按行读取数据,每一行会形成一个KV对,调用一次map方法
【千万别忘了!!!】
除了默认输入格式,其他三种输入格式都需要在Driver类中设置相关参数:
// 设置Job的输入类型
job.setInputFormatClass(......)
// 对于NLineInputFormat,要设置多少行一个切片
NLineInputFormat.setNumLinesPerSplit(job, 3);
// 对于CombineTextInputFormat要设置最大切片大小
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304); // 默认4m
// 对于KeyValueInputFormat要指定对应的分隔符 -- 每行的Key和Value用什么分割的
// 特别注意:这个参数是在Configuration中进行设置的!!
conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, "\t"); // 以制表符分割
5. MR常用的输出类型
同理,MR的输出类型(Reducer写出的文件类型)也都继承了FileOutputFormat类,同样支持自定义输出格式,需要继承FileOutputFormat类并重写RecordWriter方法
OutputFormat | Description |
---|---|
TextOutputFormat | Reducer的结果直接以字符串格式按行写出 |
SequenceFileOutputFormat | Reducer的结果按照KV键值对格式写出的序列化文件 |
SequenceFileOutputFormat中写入的是Reducer输出的键值对,且进行了序列化,因此更方便进行压缩;此外,一个MR流程得到的结果如果是SequenceFileOutputFormat格式写出,那么该结果能够很便利地作为下一个MR流程的输入,因为已经序列化封装好了KV对,作为输入时只需要进行反序列化读入就可以了
PS
思否什么时候能够支持自定义字体颜色!!!好想记出五颜六色而又骚气的笔记......o(╥﹏╥)o