Hadoop-Mapreduce
1. 扑克牌问题
- 假如你有2000副四大名著主题扑克牌。现在将他们全部混合在一起,然后从中随机抽出一部分丢
掉,现在要求你统计出每种名著分别有多少张?- 统计扑克牌人数不限(20人)
2. MapReduce设计理念
- map–>映射(key value)
- reduce–>归纳
- mapreduce必须构建在hdfs之上一种大数据离线计算框架
- 在线:实时数据处理
- 离线:数据处理时效性没有在线那么强,但是相对也需要很快得到结果
- mapreduce不会马上得到结果,他会有一定的延时
- 如果数据量小,使用mapreduce反而不合适
- 杀鸡用牛刀
- 原始数据–>map(Key,Value)–>Reduce
- 分布式计算
- 将大的数据切分成多个小数据,交给更多的节点参与运算
- 计算向数据靠拢
- 将计算传递给有数据的节点上进行工作
3. MapReduce架构
3.1. MapReduce1.x
- client
- 客户端发送mr任务到集群
- 客户端的种类有很多种
- hadoop jar wordcount.jar
- JobTracker
- 接受客户端的mr任务
- 选择一个资源丰富的,执行对应的任务
- 并且给这个任务分配资源
- 与TaskTracker保持心跳,接受汇报信息
- 接受客户端的mr任务
- TaskTracker
- 保持心跳,汇报资源
- 当前机器内存,当前机器任务数
- 当分配资源之后,开始在本机分配对应的资源给Task
- 并且实时监控任务的执行,并汇报
- 保持心跳,汇报资源
- Slot(槽)
- 属于JobTracker分配的资源
- 计算能力,IO能力…
- 不管任务大小,资源是恒定的,不灵活但是好管理
- Task(MapTask–ReduceTask)
- 开始按照MR的流程执行业务
- 当任务完成时,JobTracker告诉TaskTracker回收资源
- 缺点:
- 单点故障
- 内存扩展
- 业务瓶颈
- 只能执行MR的操作
- 如果其他框架需要运行在Hadoop上,需要独立开发自己的资源调度框架
3.2. MapReduce2.x
- 2.x开始使用Yarn(Yet Another Resource Negotiator,另一种资源协调者)统一管理资源
- 以后其他的计算框架可以直接访问yarn获取当前集群的空闲节点
- client
- 客户端发送mr任务到集群
- 客户端的种类有很多种
- hadoop jar wordcount.jar
- ResourceManager
- 资源协调框架的管理者
- 分为主节点和备用节点(防止单点故障)
- 主备的切换基于Zookeeper进行管理
- 时刻与NodeManager保持心跳,接受NodeManager的汇报
- NodeManager汇报当前节点的资源情况
- 当有外部框架要使用资源的时候直接访问ResourceManager即可
- 如果有MR任务,先去ResourceManager申请资源,ResourceManager根据汇报相对灵活分配资源
- 资源在NodeManager1,NodeManager1要负责开辟资源
- NodeManager
- Yet Another Resource Negotiator(另一种资源协调者)
- 资源协调框架的执行者
- 每一个DataNode上默认有一个NodeManager
- NodeManager汇报自己的信息到ResourceManager
- Container
- 2.x资源的代名词
- Container动态分配的
- ApplicationMaster
- 我们本次JOB任务的主导者
- 负责调度本次被分配的资源Container
- 当所有的节点任务全部完成,application告诉ResourceManager请求杀死当前ApplicationMaster线程
- 本次任务所有的资源都会被释放
- Task(MapTask–ReduceTask)
- 开始按照MR的流程执行业务
- 当任务完成时,ApplicationMaster接收到当前节点的回馈
4. Hadoop搭建yarn环境
NameNode01 | NameNode02 | DateNode | ZKFC | ZooKeeper | JournalNode | ResourceManager | NodeManager | |
---|---|---|---|---|---|---|---|---|
Node01 | * | * | * | * | * | * | * | |
Node02 | * | * | * | * | * | * | ||
Node03 | * | * | * | * | * | |||
yarn环境搭建基于前面HA环境 |
4.1. 切换工作目录
- [root@node01 ~]# cd /opt/bdp/hadoop-3.1.2/etc/hadoop/
4.2. 修改集群环境
- [root@node01 hadoop]# vim hadoop-env.sh
##继续添加用户信息
export JAVA_HOME=/usr/java/jdk1.8.0_231-amd64
export HDFS_NAMENODE_USER=root
export HDFS_DATANODE_USER=root
export HDFS_ZKFC_USER=root
export HDFS_JOURNALNODE_USER=root
export YARN_RESOURCEMANAGER_USER=root
export YARN_NODEMANAGER_USER=root
4.3. 修改配置文件
- [root@node01 hadoop]# vi mapred-site.xml
<!-- 指定mr框架为yarn方式 -->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<!-- 指定mapreduce jobhistory地址 -->
<property>
<name>mapreduce.jobhistory.address</name>
<value>node01:10020</value>
</property>
<!-- 任务历史服务器的web地址 -->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>node01:19888</value>
</property>
<!-- 配置运行过的日志存放在hdfs上的存放路径 -->
<property>
<name>mapreduce.jobhistory.done-dir</name>
<value>/history/done</value>
</property>
<!-- 配置正在运行中的日志在hdfs上的存放路径 -->
<property>
<name>mapreudce.jobhistory.intermediate.done-dir</name>
<value>/history/done/done_intermediate</value>
</property>
<property>
<name>mapreduce.application.classpath</name>
<value>
/opt/bdp/hadoop-3.1.2/etc/hadoop,
/opt/bdp/hadoop-3.1.2/share/hadoop/common/*,
/opt/bdp/hadoop-3.1.2/share/hadoop/common/lib/*,
/opt/bdp/hadoop-3.1.2/share/hadoop/hdfs/*,
/opt/bdp/hadoop-3.1.2/share/hadoop/hdfs/lib/*,
/opt/bdp/hadoop-3.1.2/share/hadoop/mapreduce/*,
/opt/bdp/hadoop-3.1.2/share/hadoop/mapreduce/lib/*,
/opt/bdp/hadoop-3.1.2/share/hadoop/yarn/*,
/opt/bdp/hadoop-3.1.2/share/hadoop/yarn/lib/*
</value>
</property>
- [root@node01 hadoop]# vi yarn-site.xml
<!-- 开启RM高可用 -->
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<!-- 指定RM的cluster id -->
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>yarn-bdp</value>
</property>
<!-- 指定RM的名字 -->
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<!-- 分别指定RM的地址 -->
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>node01</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>node03</value>
</property>
<property>
<name>yarn.resourcemanager.webapp.address.rm1</name>
<value>node01:8088</value>
</property>
<property>
<name>yarn.resourcemanager.webapp.address.rm2</name>
<value>node03:8088</value>
</property>
<!-- 指定zk集群地址 -->
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>node01:2181,node02:2181,node03:2181</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<!-- 开启日志聚合 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>86400</value>
</property>
<!-- 启用自动恢复 -->
<property>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<!-- 制定resourcemanager的状态信息存储在zookeeper集群上 -->
<property>
<name>yarn.resourcemanager.store.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
</property>
<!-- Whether virtual memory limits will be enforced for containers. -->
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-pmem-ratio</name>
<value>3</value>
</property>
4.4. 拷贝到其他节点
scp yarn-site.xml mapred-site.xml root@node02:`pwd`
scp yarn-site.xml mapred-site.xml root@node03:`pwd`
4.5. 开启集群
[123]zkServer.sh start
[root@node01 hadoop]# start-dfs.sh
[root@node01 hadoop]# start-yarn.sh
[root@node01 hadoop]# mr-jobhistory-daemon.sh start historyserver
4.6. 关机拍摄快照
[1]stop-all.sh
[1]mr-jobhistory-daemon.sh stop historyserver
[123]zkServer.sh stop
5. MR的计算流程
- 计算1T数据中每个单词出现的次数–> wordcount
5.1. 原始数据File
- 1T数据被切分成块存放在HDFS上,每一个块有128M大小
5.2. 数据块Block
- hdfs上数据存储的一个单元,同一个文件中块的大小都是相同的
- 因为数据存储到HDFS上不可变,所以有可能块的数量和集群的计算能力不匹配
- 我们需要一个动态调整本次参与计算节点数量的一个单位
- 我们可以动态的改变这个单位–》参与的节点
5.3. 切片Split
- 切片是一个逻辑概念
- 在不改变现在数据存储的情况下,可以控制参与计算的节点数目
- 通过切片大小可以达到控制计算节点数量的目的
- 有多少个切片就会执行多少个Map任务
- 一般切片大小为Block的整数倍(2 1/2)
- 防止多余创建和很多的数据连接
- 如果Split>Block ,计算节点少了
- 如果Split<Block ,计算节点多了
- 默认情况下,Split切片的大小等于Block的大小 ,默认128M
- 一个切片对应一个MapTask
5.4. MapTask
- map默认从所属切片读取数据,每次读取一行(默认读取器)到内存中
- 我们可以根据自己书写的分词逻辑(空格分隔).计算每个单词出现的次数
- 这是就会产生 (Map<String,Integer>)临时数据,存放在内存中
- 但是内存大小是有限的,如果多个任务同时执行有可能内存溢出(OOM)
- 如果把数据都直接存放到硬盘,效率太低
- 我们需要在OOM和效率低之间提供一个有效方案
- 可以现在内存中写入一部分,然后写出到硬盘
5.5. 环形数据缓冲区
可以循环利用这块内存区域,减少数据溢写时map的停止时间
- 每一个Map可以独享的一个内存区域
- 在内存中构建一个环形数据缓冲区(kvBuffer),默认大小为100M
- 设置缓冲区的阈值为80%,当缓冲区的数据达到80M开始向外溢写到硬盘
- 溢写的时候还有20M的空间可以被使用效率并不会被减缓
- 而且将数据循环写到硬盘,不用担心OOM问题
5.6. 分区Partation
- 根据Key直接计算出对应的Reduce
- 分区的数量和Reduce的数量是相等的
- hash(key) % partation = num
- 默认分区的算法是Hash然后取余
- Object的hashCode()—equals()
- 如果两个对象equals,那么两个对象的hashcode一定相等
- 如果两个对象的hashcode相等,但是对象不一定equlas
5.7. 排序Sort
- 对要溢写的数据进行排序(QuickSort)
- 按照先Partation后Key的顺序排序–>相同分区在一起,相同Key的在一起
- 我们将来溢写出的小文件也都是有序的
5.8. 溢写Spill
- 将内存中的数据循环写到硬盘,不用担心OOM问题
- 每次会产生一个80M的文件
- 如果本次Map产生的数据较多,可能会溢写多个文件
5.9. 合并Merge
- 因为溢写会产生很多有序(分区 key)的小文件,而且小文件的数目不确定
- 后面向reduce传递数据带来很大的问题
- 所以将小文件合并成一个大文件,将来拉取的数据直接从大文件拉取即可
- 合并小文件的时候同样进行排序(归并排序),最终产生一个有序的大文件
5.10. 组合器combiner
- 集群的带宽限制了mapreduce作业的数量,因此应该尽量避免map和reduce任务之间的数据传
输。hadoop允许用户对map的输出数据进行处理,用户可自定义combiner函数(如同map函数和
reduce函数一般),其逻辑一般和reduce函数一样,combiner的输入是map的输出,combiner
的输出作为reduce的输入,很多情况下可以直接将reduce函数作为conbiner函数来使用
(job.setCombinerClass(FlowCountReducer.class);)。 - combiner属于优化方案,所以无法确定combiner函数会调用多少次,可以在环形缓存区溢出文件
时调用combiner函数,也可以在溢出的小文件合并成大文件时调用combiner。但要保证不管调用
几次combiner函数都不会影响最终的结果,所以不是所有处理逻辑都可以使用combiner组件,有
些逻辑如果在使用了combiner函数后会改变最后rerduce的输出结果(如求几个数的平均值,就不
能先用combiner求一次各个map输出结果的平均值,再求这些平均值的平均值,这将导致结果错
误)。 - combiner的意义就是对每一个maptask的输出进行局部汇总,以减小网络传输量。
原先传给reduce的数据是 a1 a1 a1 a1 a1
第一次combiner组合之后变为a{1,1,1,1,…}
第二次combiner后传给reduce的数据变为a{4,2,3,5…}
5.11. 拉取Fetch
- 我们需要将Map的临时结果拉取到Reduce节点
- 原则:
- 相同的Key必须拉取到同一个Reduce节点
- 但是一个Reduce节点可以有多个Key
- 未排序前拉取数据的时候必须对Map产生的最终的合并文件做全序遍历
- 而且每一个reduce都要做一个全序遍历
- 如果map产生的大文件是有序的,每一个reduce只需要从文件中读取自己所需的即可
5.12. 合并Merge
- 因为reduce拉取的时候,会从多个map拉取数据
- 那么每个map都会产生一个小文件,这些小文件(文件与文件之间无序,文件内部有序)
- 为了方便计算(没必要读取N个小文件),需要合并文件
- 归并算法合并成2个(qishishilia)
- 相同的key都在一起
5.13. 归并Reduce
- 将文件中的数据读取到内存中
- 一次性将相同的key全部读取到内存中
- 直接将相同的key得到结果–>最终结果
5.14. 写出Output
- 每个reduce将自己计算的最终结果都会存放到HDFS上
5.15. MapReduce过程截图
6. MapReduce案例
6.1. WordCount项目
6.1.1. Java实现
- pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- Hadoop版本控制 -->
<hadoop.version>3.1.2</hadoop.version>
<!-- commons-io版本控制 -->
<commons-io.version>2.4</commons-io.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoopcommon -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${
hadoop.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoophdfs -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${
hadoop.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoopclient -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${
hadoop.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoopmapreduce-client-common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-common</artifactId>
<version>${
hadoop.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoopmapreduce-client-core -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>${
hadoop.version}<