分布式:程序多节点(resourceManager 和 nodeManager)多角色(map 和 reduce)组成
离线:计算过程中产生中间数据,会保存在硬盘上
计算框架:提供了一个编程模型,可以很轻松的实现分布式数据分析程序
使用 MapReduce 需要先启动 hdfs 和 yarn:
1. 启动 3 个节点的 zookeeper
2. 启动 hdfs
3. 启动 yarn
ResourceManager
NodeManager
http://master:8088
wordcount(词频统计):
计算单词在文章内出现的次数
计算单词在文章内出现的次数
1. 在 /root 中创建一个 word.txt 文件
2. 输入一些单词
3. 上传 word.txt 到 hdfs
4. 进入 /usr/local/hadoop/share/hadoop/mapreduce 文件夹
5. 执行词频统计程序:
hadoop jar hadoop-mapreduce-examples-2.6.0-cdh5.15.1.jar wordcount /word.txt /word-count-1
hadoop jar:执行 maperduce 程序
/word.txt:待分析的数据保存位置
/word-count-1:分析结果保存位置
如果想重复执行,先删除 /word-count-1 文件夹
6. 查看分析结果:
hadoop fs -cat /word-count-1/part-r-00000
mapreduce 执行流程:
采用分治法的思想进行大数据分析
分治法:把大任务拆分为许多小任务,交给不同的节点去执行,汇总各自计算结果
分为两个阶段:map 和 reduce
map:各个节点执行小任务
reduce:汇总各个节点的计算结果
mapreduce 程序中的输入和输出都是 key-value
map:
输入 key:行首字母的索引
输入 value:每一行数据
输出 key:每个单词
输出 value:1
聚合 key 相同的 value 到一起,形成新的 key-value,输入给 reduce
hello 1
hello 1 ---> hello [1, 1, 1]
hello 1
reduce:
输入 key:每个单词
输入 value:value 集合
输出 key:每个单词
输出 value:词频
map 的输出就是 reduce 的输入,reduce 用来汇总 map 输出的内容
reduce 的输出就是数据分析的结果
wordcount 的代码:
每个 mapreduce 程序都分为 3 部分:
map:小任务的数据分析逻辑
reduce:汇总数据的逻辑
job:拼装 map 和 reduce 组成可执行程序
每个 mapper 的计算结果保存在硬盘上,reducer 从硬盘上获取数据进行汇总
在这个过程中就有序列化和反序列化操作
序列化:对象转换为二进制
反序列化:二进制转换为对象
Java 自带的序列化机制(Serializeable)会在序列化的时候生成一些冗余信息
比如:版本号,class 结构,...
严重拖累了程序的执行效率,为了解决这个问题 MapReduce 框架自己实现了一套序列化机制
int IntWritable
long LongWritable
String Text
Null NullWritable
mapper:
1. 创建静态内部类 WordCountMapper 继承自 Mapper
2. 填写输入输出 kv 的四个类型
3. 声明输出的 kv 对象
4. 使用 ctrl + o 重写父类 map 方法,完成数据计算工作
reducer:
1. 创建静态内部类 WordCountReducer 继承自 Reducer
2. 填写输入输出 kv 的四个类型
3. 声明输出的 kv 对象
4. 使用 ctrl + o 重写父类 reduce 方法,完成数据计算工作
job:
1. psvm 生成 main 方法
2. 配置 hdfs 访问地址
3. 获取 job 对象,并捕获异常
4. 设置 job 名字和主类(main 方法所在类)
5. 设置 mapper 和 reducer 类
6. 设置 mapper 的 kv 输出类型
7. 设置 reducer 的 kv 输出类型
8. 设置待计算的数据位置
9. 设置计算结果的保存位置
10. 提交 job,等待执行结果,需要捕获异常
如果 mapper 和 reducer 的输出 kv 类型一致,可以省略第 6 步
MapReduce 程序设计:
map 的输入 kv 是框架给的固定格式:LongWritable, Text
reduce 的输出 kv 是用户事先定义好的固定格式:
我们需要设计的是 map 的输出和 reduce 的输入 kv,而两者又保持一致
只需要设计一组 kv 即可
(LongWritable, Text) --> kv --> (?, ?)
MapReduce 的流程:
1. split(切片):
把大任务拆分为小任务,交给不同的 map 执行
通常一个 split 的大小为 block size
一个 split 对应一个 map 程序
2. 原始数据经过 inputFormat 转换为 kv 输入给 map
3. map 调用 map 方法依次处理每组 kv
4. map 输出计算结果到内存缓冲区,达到 80% 的时候一次性写出数据到硬盘
可以减少 IO 操作,调高效率,节省时间
在数据从内存写入硬盘期间会对 kv 进行分区和排序操作
partition:决定 kv 交给哪个 reduce 进行处理
sort:按照 key 进行升序排列
5. 合并每次写出的小文件为一个大文件,并保证分区且有序
在此期间还会进行排序,还可能执行 combiner 操作
combiner:可以看作是 map 端的一个 reduce
对每个 map 的计算结果进行汇总
然后把汇总结果交给 reduce 再次汇总
6. 每个 reduce 从 map 中下载需要处理的 kv
7. reduce 合并从每个 map 下载的数据为一个文件
在此期间还会进行排序和聚合,形成新的 kv
聚合:key 相同的 value 合并到一个集合中
8. reduce 调用 reduce 方法依次处理新的 kv
9. 通过 outputFormat 输出计算结果到 HDFS
一个 reduce 对应一个结果文件
map 阶段:3,4,5
reduce 阶段:6,7,8
溢写:4
shuffle:4,5,6,7
以上过程中的所有排序都是对 key 进行升序排列
MapReduce 的combiner和partition:
combiner:可以看作是 map 端的一个 reduce
对每个 map 的计算结果进行汇总
然后把汇总结果交给 reduce 再次汇总
减少发送给 reduce 的数据,提高执行效率
是否执行取决于执行 map 任务节点的状态,
在节点压力大的情况下会跳过 combiner
combiner 的输入是 map 的输出,输出是 reduce 的输入
所以 combiner 的输入输出类型保持一致
partition(分区):
把不同的 kv 交给不同的 reduce 进行处理
默认只有 1 个 reduce
默认分区策略是 HashPartitioner :
使用 key 的 hashCode 对 reduce 的个数取余,结果作为对应的 reduce 编号
hashCode 是对象的唯一标识
自定义分区:
1. 创建类继承 Partitioner,设置泛型为 map 输出的 kv 类型
2. 实现 getPartition 方法,返回 reduce 编号
3. 在 job 中配置使用自定义分区:
设置自定义分区类:
job.setPartitionerClass()
设置 reduce 的个数
job.setNumReduceTasks()
数据格式转换:
map: (K1, V1) → list(K2,V2)
combine: (K2, list(V2)) → list(K3, V3)
reduce: (K3, list(V3)) → list(K4, V4)
注意:combine的输入和reduce的完全一致,输出和map的完全一致