Hadoop
Hadoop
一、简介
Hadoop是什么?
1)Hadoop是一个由Apache基金会所开发的分布式系统基础架构
2)主要解决海量数据的存储和海量数据的分析计算问题。
3)广义上来说,HADOOP通常是指一个更广泛的概念——HADOOP生态圈
Hadoop前身是Luncene,Lucene框架是Doug Cutting开创的开源软件,用Java书写代码,实现与Google类似的全文搜索功能,它提供了全文检索引擎的架构,包括完整的查询引擎和索引引擎,但是对于海量数据的场景,Lucene面对与Google同样的困难,存储数据困难,检索速度慢,学习和模仿Google解决这些问题的办法 :微型版Nutch,可以说Google是Hadoop的思想之源(Google在大数据方面的三篇论文):GFS --->HDFS、Map-Reduce --->MR、BigTable --->HBase。
2003-2004年,Google公开了部分GFS和MapReduce思想的细节,以此为基础Doug Cutting等人用了2年业余时间实现了DFS和MapReduce机制,使Nutch性能飙升。
2005 年Hadoop 作为 Lucene的子项目 Nutch的一部分正式引入Apache基金会。
2006 年 3 月份,Map-Reduce和Nutch Distributed File System (NDFS) 分别被纳入到 Hadoop 项目中,Hadoop就此正式诞生,标志着大数据时代来临。
Hadoop官网:http://hadoop.apache.org/
截至目前Hadoop版本分为三代:
第一代:Hadoop称为Hadoop 1.0,是从0.20.x版本演化来的
第二代:Hadoop称为Hadoop2.0,该版本完全不同于Hadop 1. 0,是一套全新的架构,增加了NameNode HA,Federation和YARN资源管理系统
第三代:Hadoop称为Hadoop 3.0,引入了很多新的特性,性能也有新的提升,是目前最新版本
Hadoop的优点:
扩容能力(Scalable)
• Hadoop是在可用的计算机集群间分配数据并完成计算任务的,这些集群可用方便的扩展到数以千计个节点中
成本低(Economical)
• Hadoop服务器集群来分发以及处理数据,以至于成本很低
高效率(Efficient)
• 通过并发数据,Hadoop可以在节点之间动态并行的移动数据,使得速度非常快
可靠性(Rellable)
• 能自动维护数据的多份复制,并且在任务失败后能自动地重新部署(redeploy)计算任务。所以Hadoop的按位存储和处理数据的能力值得人们信赖
Hadoop的组成
• Hadoop HDFS:一个高可靠、高吞吐量的分布式文件系统。
• Hadoop MapReduce:一个分布式的离线并行计算框架。
• Hadoop YARN:作业调度与集群资源管理的框架。
• Hadoop Common:支持其他模块的工具模块。
Hadoop生态
1)Sqoop:sqoop是一款开源的工具,主要用于在Hadoop(Hive)与传统的数据库(mysql)间进行数据的传递,可以将一个关系型数据库(例如:MySQL,Oracle等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。
2)Flume:Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
3)Kafka:Kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性:
a、通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。
b、高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒数百万的消息
c、支持通过Kafka服务器和消费机集群来分区消息。
d、支持Hadoop并行数据加载。
4)Storm:Storm为分布式实时计算提供了一组通用原语,可被用于“流处理”之中,实时处理消息并更新数据库。这是管理队列及工作者集群的另一种方式。Storm也可被用于“连续计算”(continuous computation),对数据流做连续
查询,在计算时就将结果以流的形式输出给用户。
5)Spark:Spark是当前最流行的开源大数据内存计算框架。可以基于Hadoop上存储的大数据进行计算。
6)Oozie:Oozie是一个管理Hdoop作业(job)的工作流程调度管理系统。Oozie协调作业就是通过时间(频率)和有效数据触发当前的Oozie工作流程。
7)Hbase:HBase是一个分布式的、面向列的开源数据库。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。
8)Hive:hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。 其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统
计分析。
9)Mahout:
Apache Mahout是个可扩展的机器学习和数据挖掘库,当前Mahout支持主要的4个用例:
推荐挖掘:搜集用户动作并以此给用户推荐可能喜欢的事物。
聚集:收集文件并进行相关文件分组。
分类:从现有的分类文档中学习,寻找文档中的相似特征,并为无标签的文档进行正确的归类。
频繁项集挖掘:将一组项分组,并识别哪些个别项会经常一起出现。
10)R语言:R是用于统计分析、绘图的语言和操作环境。R是属于GNU系统的一个自由、免费、源代码开放的软件,它是一个用于统计计算和统计制图的优秀工具。
11)ZooKeeper:Zookeeper是Google的Chubby一个开源的实现。它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、 分布式同步、组服务等。ZooKeeper的目标就是封装好复杂易出错的关键服务,将简
单易用的接口和性能高效、功能稳定的系统提供给用户。
Hadoop组成
Hadoop是由底层的HDFS分布式文件系统和上层YARN资源管理器+MapReduce计算框架 组成
hdfs
hdfs是一个高可靠、高吞吐量的分布式文件系统,适合一次写入,多次读出,其内部核心组件有以下三个:
1)NameNode(nn):存储文件的元数据,如文件名,文件目录结构,文件属性(生成时间、副本数、文件权限),以及每个文件的块列表和块所在的DataNode等。
2)DataNode(dn):在本地文件系统存储文件块数据,以及块数据的校验和。
3)Secondary NameNode(2nn):每隔一段时间对NameNode元数据备份。
yarn
yarn是hadoop2.0之后引入的负责作业调度与集群资源管理的框架,其内部核心组件有以下四个:
1)ResourceManager(RM):
a、处理客户端请求
b、监控NodeManager
c、启动或监控ApplicationMaster
d、资源的分配与调度
2)NodeManager(NM):
a、管理单个节点上的资源
b、处理来自ResourceManager的命令
c、处理来自ApplicationMaster的命令
3)ApplicationMaster(AM):
a、负责数据的切分
b、为应用程序申请资源并分配给内部的任务
c、任务的监控与容错
4)Container:Container是YARN中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等。
mapreduce:
MapReduce将计算过程分为两个阶段:Map和Reduce
1)Map阶段并行处理输入数据
2)Reduce阶段对Map结果进行汇总
HDFS、YARN、MapReduce三者关系:
常用端口号:
hadoop3.x
hdfs NameNode内部端口:8020、9000、9820
hdfs对用户的查询端口:9870
yarn查看任务运行情况:8088
历史服务器:19888
hadoop2.x
hdfs NameNode内部端口:8020、9000
hdfs对用户的查询端口:50070
常用的配置文件:
3.x:core-site.xml hdfs-site.xml yarn-site.xml mapred-site.xml workers
2.x: core-site.xml hdfs-site.xml yarn-site.xml mapred-site.xml slaves
部署
单机部署
1)设置节点之间免密登录
#生成密钥对
ssh-keygen -t rsa
#分发公钥到其他主机
ssh-copy-id username@remote_host
2)安装Java环境
#查询现有Java版本
java -version
#若需要卸载重新安装:
#查看已经安装版本
rpm -qa|grep java
#卸载
rpm -e --nodeps java-1.8.0-openjdk-1.8.0.102-4.b14.el7.x86_64
rpm -e --nodeps java-1.8.0-openjdk-headless-1.8.0.102-4.b14.el7.x86_64
rpm -e --nodeps java-1.7.0-openjdk-headless-1.7.0.111-2.6.7.8.el7.x86_64
#查看卸载情况
java -version
#安装
mkdir -p /usr/local/soft
cd /usr/local/soft/
tar -zxvf jdk-8u11-linux-x64.tar.gz
#配置环境变量,/etc/profile是全局配置文件,系统中的所有用户都会读取该文件中定义的环境变量,若只想让Java环境变量对某个用户生效,可以将下面两行写到该用户家目录的.bashrc里面
vim /etc/profile
export JAVA_HOME=/usr/local/soft/jdk1.8.0_11
export PATH=.:$PATH:$JAVA_HOME/bin
#环境变量生效
source /etc/profile
#验证是否安装
java -version
3)Hadoop部署
#1、上传软件包并解压
cd /usr/local/soft/
tar -zxvf hadoop-3.2.0.tar.gz
#2、修改hadoop的Java配置路径,配置路径要与上面Java的安装环境一样
vi /usr/local/soft/hadoop-3.2.0/etc/hadoop/hadoop-env.sh
#文件末尾添加下面行
export JAVA_HOME=/usr/local/soft/jdk1.8.0_11
export HDFS_NAMENODE_USER=root
export HDFS_DATANODE_USER=root
export HDFS_SECONDARYNAMENODE_USER=root
export YARN_RESOURCEMANAGER_USER=root
export YARN_NODEMANAGER_USER=root
export HADOOP_PID_DIR=/data/hadoop/pids
export HADOOP_LOG_DIR=/data/hadoop/logs
#3、添加系统环境变量,同第三步配置Java的环境变量,若只想Java环境只对某用户生效,可以配置该用户家目录的.bashrc
vi /etc/profile
export HADOOP_HOME=/usr/local/soft/hadoop-3.2.0
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin
export LD_LIBRARY_PATH=$HADOOP_HOME/lib/native
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
#环境变量生效
source /etc/profile
#4、修改配置文件
#进入/usr/local/soft/hadoop-3.2.0/etc/hadoop/目录,修改core-site.xml
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://hadoop100:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/data/hadoop/tmp</value>
</property>
<property>
<name>dfs.webhdfs.enabled</name>
<value>true</value>
</property>
</configuration>
#修改hdfs-site.xml
#dfs.namenode.secondary.http-address是指定secondaryNameNode的http访问地址和端口号,因为在规划中,我们将master1规划为SecondaryNameNode服务器。
<configuration>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>hadoop100:50090</value>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>file:/data/hadoop/hdfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file:/data/hadoop/hdfs/data</value>
</property>
<property>
<name>dfs.permissions.enabled</name>
<value>false</value>
</property>
<property>
<name>hadoop.proxyuser.root.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.root.groups</name>
<value>*</value>
</property>
</configuration>
#Workers中添加
#workers文件是指定HDFS上有哪些DataNode节点
hadoop100
#修改Yarn-site.xml
#根据规划yarn.resourcemanager.hostname这个指定resourcemanager服务器指向master1。yarn.log-aggregation-enable是配置是否启用日志聚集功能。yarn.log-aggregation.retain-seconds是配置聚集的日志在HDFS上最多保存多长时间。
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.localizer.address</name>
<value>0.0.0.0:8140</value>
</property>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>hadoop100</value>
</property>
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
<property>
<name>yarn.log.server.url</name>
<value>http://hadoop100:19888/jobhistory/logs</value>
</property>
</configuration>
#修改Maperd-site.xml
#mapreduce.framework.name设置mapreduce任务运行在yarn上。mapreduce.jobhistory.address是设置mapreduce的历史服务器安装在master1机器上。mapreduce.jobhistory.webapp.address是设置历史服务器的web页面地址和端口号
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.app.mapreduce.am.env</name> <value>HADOOP_MAPRED_HOME=/usr/local/soft/hadoop-3.2.0</value>
</property>
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/soft/hadoop-3.2.0</value>
</property>
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/soft/hadoop-3.2.0</value>
</property>
<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop100:10020</value>
</property>
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value> hadoop100:19888</value>
</property>
</configuration>
#5、格式化Hadoop集群
#!!!!!!!!!!!!!!!!!!!!!!!!!!注意:如果是集群配置,下面几步不要做!!!!!!!!!!!!!!!!!!!!!!!!!!
hdfs namenode -format
#6、启动hdfs和yarn
start-all.sh
#若要停止
stop-all.sh
#7、检测
#进程
jps
集群部署
#先将单机配置部署打包发送到集群其他主机上
#1、修改主机名
vi /etc/hostname或hostnamectl set-hostname 主机名
#2、修改IP映射
vi /etc/hosts
#3、修改workers
vi /usr/local/soft/hadoop-3.2.0/etc/hadoop/workers
#将集群的主机名全部写入
#4、设置集群之间免密登录
#5、格式化系统
#将三个服务器上的/data全部删除
rm -rf /data
#hadoop100服务器上执行
hdfs namenode -format
#启动
./sbin/start-all.sh
二、HDFS详解
HDFS概述
HDFS,即Hadoop分布式文件系统(Hadoop Distributed File System),是Hadoop的核心组件之一,作为底层分布式存储服务,主要解决大数据存储问题。分布式文件系统的优势在于能够跨越多台计算机实现数据存储,在大数据时代具有广泛的应用前景。HDFS起源于Google的GFS(Google File System)论文,这篇论文于2003年10月发布。GFS、MapReduce和BigTable曾被称为Google的“旧三驾马车”。HDFS可以看作是GFS的克隆版,继承了GFS的许多设计理念。
HDFS的架构
HDFS采用主从结构设计,系统中包含一个主节点(NameNode)和多个从节点(DataNode)。在HDFS中,文件被物理上分块存储,每个块的大小为128MB。NameNode负责维护整个文件系统的目录树以及每个文件块(Block)的位置信息,而DataNode则负责实际存储这些文件块,并定期向NameNode汇报它们的状态。
HDFS的设计目标
HDFS在设计时考虑了以下几个关键目标:
- 硬件故障是常态:HDFS通常由成百上千台服务器组成,每个组件都可能发生故障。因此,HDFS的核心架构目标之一是检测故障并自动快速恢复。
- 流式数据访问:HDFS上的应用主要是以流式方式读取数据,适合批量处理,而非用户交互式应用。设计上更注重数据访问的高吞吐量,而非响应时间。
- 支持大文件:HDFS针对GB到TB级别的大文件进行了优化,旨在提供高聚合数据带宽,支持数百个节点的集群,并能够处理数千万级别的文件。
- 一次写入,多次读取:大部分HDFS应用采用写一次、读多次的访问模型。文件一旦创建、写入、关闭后,通常不再需要修改。这一特性简化了数据一致性问题,并提升了数据访问的吞吐量。
- 移动计算优于移动数据:HDFS的设计理念是将计算移动到数据所在位置,而不是将数据移动到计算所在位置。这在处理海量数据时尤为重要。
- 跨平台可移植性:HDFS能够在异构硬件和软件平台上运行,这使得大数据应用更广泛地采用HDFS作为其存储平台。
HDFS的优缺点
优点:
- 高容错性:
- HDFS通过自动保存多个数据副本来提高容错性。如果一个副本丢失,系统能够自动恢复。
- 适合大数据处理:
- HDFS能够处理规模达到GB、TB甚至PB级别的数据,并且支持处理百万级别以上数量的文件。
- 低成本高可靠性:
- HDFS可以构建在廉价机器上,并通过多副本机制提高系统的可靠性。
缺点:
- 不适合低延时数据访问:HDFS不适用于需要毫秒级响应的存储需求。
- 无法高效存储大量小文件:
- 大量小文件会占用NameNode大量内存来存储文件、目录和块信息,而NameNode的内存资源是有限的。
- 存储小文件的寻址时间往往超过读取时间,这违背了HDFS的设计目标。
- 并发写入与文件随机修改受限:
- HDFS不支持多个线程同时写入同一文件,一个文件只能由一个线程写入。
- HDFS仅支持数据追加(append),不支持对文件的随机修改。
HDFS的设计充分考虑了大数据存储的特点与需求,尽管存在一些局限性,但它在处理大规模数据方面表现出色,已成为大数据存储领域的重要工具。
HDFS成员
HDFS架构概述
HDFS(Hadoop分布式文件系统)采用了主从(master/slave)架构,通常由一个主节点(NameNode)和若干从节点(DataNode)组成。NameNode作为HDFS集群的核心,负责管理文件系统的元数据;而DataNode则负责实际的数据存储工作。这种架构设计使得两种角色各司其职,共同协调完成分布式的文件存储服务
HDFS核心成员
HDFS—Client
- 文件切分:当文件上传到HDFS时,Client将文件切分为多个Block(块)。
- 与NameNode交互:Client获取文件的位置信息,并与NameNode交互以获取元数据。
- 与DataNode交互:Client与DataNode交互以读取或写入数据。
- 管理HDFS:Client提供命令来管理HDFS,如启动或关闭HDFS。
- 访问HDFS:Client通过命令访问HDFS中的文件和目录。
- HDFS—NameNode
- 核心作用:NameNode是HDFS的核心,被称为Master,仅存储HDFS的元数据,包括文件系统中的目录树以及每个文件的块列表和位置。
- 元数据管理:NameNode仅存储元数据,不存储实际数据或数据集。数据本身实际存储在DataNode中。
- 文件重建:NameNode知道HDFS中任何给定文件的块列表及其位置,利用这些信息,它可以从块中重建文件。
- 重要性与单点故障:NameNode对HDFS至关重要,当NameNode关闭时,整个HDFS集群无法访问。NameNode通常配置有大量内存(RAM),但也是系统中的单点故障。
元数据
-
元数据:描述HDFS上真实数据的信息,如文件路径、副本数、每个副本所在的DataNode等。这些信息保存在NameNode上。
-
元数据存储方式:
- 内存元数据:将元数据保存在内存中,方便快速读取。
- fsimage:内存中的元数据序列化到磁盘中,作为元数据镜像文件,存储某一时段NameNode内存中的元数据信息。
- edits:记录用户的操作日志,如删除请求、上传请求等,可以通过日志恢复元数据。
SecondaryNameNode
-
辅助管理:SecondaryNameNode辅助NameNode管理fsimage和edits文件,负责定期合并这些文件。
-
工作流程:
- SecondaryNameNode通知NameNode切换edits文件。
- SecondaryNameNode从NameNode获取fsimage和edits文件。
- SecondaryNameNode将fsimage载入内存,并合并edits文件。
- SecondaryNameNode将新的fsimage发回给NameNode。
- NameNode用新的fsimage替换旧的fsimage。
HDFS—DataNode
- 数据存储:DataNode负责将实际数据存储在HDFS中,被称为Slave节点。
- 通信与报告:NameNode和DataNode之间保持持续通信,DataNode启动时会向NameNode报告自己负责持有的块列表。
- 容错与恢复:如果某个DataNode失效,NameNode会安排由其他DataNode管理的块进行副本复制,不影响集群的可用性。
- 硬件配置:DataNode通常配置有大量硬盘空间,因为实际数据存储在DataNode中。
- 心跳机制:DataNode会定期向NameNode发送心跳(默认每3秒),如果NameNode长时间没有接收到心跳信号,就会认为该DataNode失效。
块与副本集
- 块存储:所有文件都以Block(块)的方式存放在HDFS中。Hadoop 1.x版本中,Block的默认大小是64MB,而在Hadoop 2.x和3.x版本中,默认大小为128MB。块的大小可以通过
hdfs-site.xml
配置文件指定。 - 副本机制:为了提高容错性,文件的所有Block都会有副本。副本数目可以在文件创建时指定,也可以在之后更改。副本机制确保即使某个DataNode发生故障,数据仍然可以从其他DataNode的副本中恢复。
元数据
- 元数据:描述HDFS上真实数据的信息,如文件路径、副本数、每个副本所在的DataNode等。这些信息保存在NameNode上。
- 元数据存储方式:
- 内存元数据:将元数据保存在内存中,方便快速读取。
- fsimage:内存中的元数据序列化到磁盘中,作为元数据镜像文件,存储某一时段NameNode内存中的元数据信息。
- edits:记录用户的操作日志,如删除请求、上传请求等,可以通过日志恢复元数据。
SecondaryNameNode
-
辅助管理:SecondaryNameNode辅助NameNode管理fsimage和edits文件,负责定期合并这些文件。
-
工作流程:
- SecondaryNameNode通知NameNode切换edits文件。
- SecondaryNameNode从NameNode获取fsimage和edits文件。
- SecondaryNameNode将fsimage载入内存,并合并edits文件。
- SecondaryNameNode将新的fsimage发回给NameNode。
- NameNode用新的fsimage替换旧的fsimage。
HDFS—DataNode
- 数据存储:DataNode负责将实际数据存储在HDFS中,被称为Slave节点。
- 通信与报告:NameNode和DataNode之间保持持续通信,DataNode启动时会向NameNode报告自己负责持有的块列表。
- 容错与恢复:如果某个DataNode失效,NameNode会安排由其他DataNode管理的块进行副本复制,不影响集群的可用性。
- 硬件配置:DataNode通常配置有大量硬盘空间,因为实际数据存储在DataNode中。
- 心跳机制:DataNode会定期向NameNode发送心跳(默认每3秒),如果NameNode长时间没有接收到心跳信号,就会认为该DataNode失效。
块与副本集
- 块存储:所有文件都以Block(块)的方式存放在HDFS中。Hadoop 1.x版本中,Block的默认大小是64MB,而在Hadoop 2.x和3.x版本中,默认大小为128MB。块的大小可以通过
hdfs-site.xml
配置文件指定。 - 副本机制:为了提高容错性,文件的所有Block都会有副本。副本数目可以在文件创建时指定,也可以在之后更改。副本机制确保即使某个DataNode发生故障,数据仍然可以从其他DataNode的副本中恢复。
HDFS读写流程
HDFS写入流程
- 文件上传请求:
- 客户端通过
Distributed FileSystem
模块向NameNode
发送上传文件的请求。NameNode
检查目标文件是否已存在以及父目录是否存在。
- 客户端通过
- 检查结果返回:
NameNode
返回确认信息,告知客户端是否可以继续上传。
- 请求上传位置:
- 客户端请求
NameNode
提供第一个Block
应上传到哪些DataNode
服务器。
- 客户端请求
- 返回
DataNode
列表:NameNode
返回三个DataNode
节点的列表,假设为dn1
、dn2
、dn3
。
- 建立数据传输管道:
- 客户端通过
FSDataOutputStream
模块请求dn1
上传数据。dn1
接收请求后,依次调用dn2
和dn3
,建立一个传输数据的通信管道。
- 客户端通过
- 节点应答:
dn1
、dn2
、dn3
逐级向客户端应答,确认管道的建立。
- 数据传输:
- 客户端开始将第一个
Block
上传到dn1
。上传时,客户端先从磁盘读取数据并存入本地内存缓存,以Packet
为单位进行传输。dn1
接收到Packet
后,将其传给dn2
,dn2
再传给dn3
。dn1
每传输一个Packet
,会将其放入应答队列中,等待应答。
- 客户端开始将第一个
- Block 完成:
- 当一个
Block
传输完成后,客户端再次请求NameNode
提供上传第二个Block
的DataNode
服务器,重复步骤 3 到 7,直到所有数据上传完成。
- 当一个
HDFS读取流程
当客户端从 HDFS 下载文件时,以下是详细的读取流程:
- 文件下载请求:
- 客户端通过
DistributedFileSystem
向NameNode
发送下载文件的请求。NameNode
通过查询元数据,找到文件块所在的DataNode
地址。
- 客户端通过
- 选择
DataNode
:- 客户端根据就近原则(并随机挑选),选择一台
DataNode
服务器并请求读取数据。
- 客户端根据就近原则(并随机挑选),选择一台
- 数据传输:
DataNode
从磁盘中读取数据,并以Packet
为单位传输给客户端。传输过程中,DataNode
会对数据进行校验。
- 数据接收:
- 客户端逐个接收
Packet
,先将其存入本地缓存,然后写入目标文件。整个过程是串行读取。
- 客户端逐个接收
HDFS常用命令
Hadoop 分布式文件系统 (HDFS) 提供了一系列命令,用于管理文件和目录。以下是一些常用的 HDFS 命令及其功能和示例:
-ls
-
使用方法:
hadoop fs -ls [-h] [-R] <args>
-
功能: 显示文件、目录信息。
-
示例:
hadoop fs -ls /user
-mkdir
-
使用方法:
hadoop fs -mkdir [-p] <paths>
-
功能: 在 HDFS 上创建目录,
-p
表示会创建路径中的各级父目录。 -
示例:
hadoop fs -mkdir -p /user/hadoop/dir1 hadoop fs -mkdir hdfs://hadoop:9000/test
-put
-
使用方法:
hadoop fs -put [-f] [-p] [ -|<localsrc1> .. ] <dst>
-
功能: 将单个或多个源文件从本地文件系统复制到目标文件系统。
-p
: 保留访问和修改时间,所有权和权限。-f
: 覆盖目的地(如果已存在)。
-
示例:
hadoop fs -put /usr/local/data/a /user/hadoop/dir1 hadoop fs -put -f /usr/local/data/a /user/hadoop/dir1
-get
-
使用方法:
hadoop fs -get [-ignorecrc] [-crc] [-p] [-f] <src> <localdst>
-ignorecrc
: 跳过对下载文件的 CRC 检查。-crc
: 为下载的文件写入 CRC 校验和。
-
功能: 将文件从 HDFS 复制到本地文件系统。
-
示例:
hadoop fs -get hdfs://hadoop100:9000/user/hadoop/dir1/test1.txt /usr/local/data/log
-appendToFile
-
使用方法:
hadoop fs -appendToFile <localsrc> ... <dst>
-
功能: 追加一个文件到已经存在的文件末尾。
-
示例:
hadoop fs -appendToFile /usr/local/data/log /user/hadoop/dir1/test1.txt
-cat
-
使用方法:
hadoop fs -cat [-ignoreCrc] URI [URI ...]
-
功能: 显示文件内容到标准输出 (stdout)。
-
示例:
hadoop fs -cat /user/hadoop/dir1/test1.txt
-tail
-
使用方法:
hadoop fs -tail [-f] URI
-f
: 在文件增长时输出附加数据。
-
功能: 将文件的最后一千字节内容显示到 stdout。
-
示例:
hadoop fs -tail /user/hadoop/dir1/test1.txt
-chgrp
-
使用方法:
hadoop fs -chgrp [-R] GROUP URI [URI ...]
-
功能: 更改文件的组关联。用户必须是文件的所有者,或者是超级用户。
-R
: 递归更改目录结构下的所有文件和目录。
-
示例:
hadoop fs -chgrp root /user/hadoop
-chmod
-
功能: 更改文件的权限。
-R
: 递归更改目录结构下的所有文件和目录。
-
示例:
hadoop fs -chmod 777 /user/hadoop/dir1/test1.txt
-chown
-
功能: 更改文件的所有者。
-R
: 递归更改目录结构下的所有文件和目录。
-
示例:
hadoop fs -chown root /user/hadoop
-copyFromLocal
-
使用方法:
hadoop fs -copyFromLocal <localsrc> URI
-
功能: 从本地文件系统中拷贝文件到 HDFS 路径。
-
示例:
hadoop fs -copyFromLocal /usr/local/data/log1 /user/hadoop
-copyToLocal
-
功能: 从 HDFS 拷贝到本地文件系统。
-
示例:
hadoop fs -copyToLocal /user/hadoop/test1.txt /usr/local/data/log2
-cp
-
功能: 从 HDFS 的一个路径拷贝到 HDFS 的另一个路径。
-
示例:
hadoop fs -cp /user/hadoop/dir1/test1.txt /user/hadoop/dir1/test1
-mv
-
功能: 在 HDFS 目录中移动文件。
-
示例:
hadoop fs -mv /user/hadoop/dir1/test1.txt /user/hadoop/dir1/test2
-rm
-
功能: 删除指定的文件或非空目录。
-r
: 递归删除。
-
示例:
hadoop fs -rm -r /user/hadoop/dir1 -skipTrash:绕过垃圾桶,直接删除 hadoop fs -rm -r -skipTrash /user/hadoop/dir1
在 HDFS 中,删除文件或目录时,如果不加 -skipTrash,Hadoop 会将文件移动到回收站目录(通常是
/user/<username>/.Trash
)。这是为了提供一定的保护机制,防止意外删除文件。如果需要恢复文件,可以从回收站中找回/user/<username>/.Trash
目录结构如下:/user/<username>/.Trash/ ├── Current # 删除的文件会存储在这里 └── checkpoint # 定时清理机制使用的标记
可以使用以下命令查看
.Trash
中的文件和目录hadoop fs -ls /user/hduser/.Trash/Current
查找到误删除数据后,通过以下命令恢复:
hadoop fs -mv /user/hduser/.Trash/Current/user/data/2024-04-24 /user/data/
-df
-
功能: 统计文件系统的可用空间信息。
-
示例:
hadoop fs -df -h /
-du
-
功能: 显示目录中所有文件的大小。当只指定一个文件时,显示此文件的大小以及副本的总大小。
-
示例:
hadoop fs -du /user/hadoop hadoop fs -du /user/hadoop/dir1/test1.txt hadoop fs -du -h /user/hadoop/dir1/test1.txt
-setrep
-
功能: 改变一个文件的副本系数。
-R
: 用于递归改变目录下所有文件的副本系数。
-
示例:
hadoop fs -setrep 10 /user/hadoop
-help
-
功能: 查看帮助。
-
示例:
hadoop fs -help rm
-count
-
功能: 查看文件夹及文件的数量。第一个参数是文件夹的数量,文件夹自身也会被认为是一个文件。
-
示例:
hadoop fs -count /user
-touchz
-
功能: 创建一个空文件。
-
示例:
hadoop fs -touchz /user/hadoop/startup
除了文件管理命令外,HDFS还提供了集群管理类命令用于管理和维护 HDFS 集群,以下是一些常用的 HDFS 集群管理类命令及其功能和示例:
-report
-
功能: 查看集群内所有节点状态。
-
示例:
hdfs dfsadmin -report
-safemode
-
使用方法:
hdfs dfsadmin -safemode [ get|enter|leave ]
-
功能: 查看hdfs是否处于安全模式。
-
示例:
hdfs dfsadmin -safemode get #获取 HDFS 当前是否处于安全模式(只读模式) hdfs dfsadmin -safemode enter #将 HDFS 进入安全模式 hdfs dfsadmin -safemode leave #退出 HDFS 的安全模式
-refreshNodes
-
功能: 刷新 DataNode 列表,更新新增或移除的节点。
-
示例:
hdfs dfsadmin -refreshNodes
-printTopology
-
功能: 显示集群的网络拓扑结构。
-
示例:
hdfs dfsadmin -printTopology
-finalizeUpgrade
-
功能: 完成 HDFS 升级,使其不可逆。
-
示例:
hdfs dfsadmin -finalizeUpgrade
-allowSnapshot
-
使用方法:
hdfs dfsadmin -allowSnapshot <path>
-
功能: 允许在指定路径创建快照。
-
示例:
hdfs dfsadmin -allowSnapshot /user/data/test
-disallowSnapshot
-
使用方法:
hdfs dfsadmin -disallowSnapshot <path>
-
功能: 禁止在指定路径创建快照。
-
示例:
hdfs dfsadmin -disallowSnapshot /user/data/test
-saveNamespace
-
功能: 强制 NameNode 将其内存中的元数据写入磁盘。
-
示例:
hdfs dfsadmin -saveNamespace
-restoreFailedStorage
-
功能: 恢复因故障而被标记为失败的存储目录。
-
示例:
hdfs dfsadmin -restoreFailedStorage
-setQuota
-
使用方法:
hdfs dfsadmin -setQuota <N> <path>
-
功能: 设置目录的文件数量配额,即最多能在该目录下创建的文件和目录总数。
-
示例:
hdfs dfsadmin -setQuota 1000 /user/hadoop
-clrQuota
-
使用方法:
hdfs dfsadmin -clrQuota <path>
-
功能: 清除目录的文件数量配额。
-
示例:
hdfs dfsadmin -clrQuota /user/hadoop
-setSpaceQuota
-
使用方法:
hdfs dfsadmin -setSpaceQuota <N> <path>
<N>
可以用具体的数值,结合 K (KB), M (MB), G (GB) 等单位。
-
功能: 设置目录的空间配额,即该目录及其子目录下文件占用的总空间。
-
示例:
hdfs dfsadmin -setSpaceQuota 100G /user/hadoop
-clrSpaceQuota
-
使用方法:
hdfs dfsadmin -clrSpaceQuota <path>
-
功能: 清除目录的空间配额。
-
示例:
hdfs dfsadmin -clrSpaceQuota /user/hadoop
-refreshServiceAcl
-
功能: 刷新服务的访问控制列表(ACL)。
-
示例:
hdfs dfsadmin -refreshServiceAcl
-refreshUserToGroupsMappings
-
功能: 刷新用户到组的映射关系。
-
示例:
hdfs dfsadmin -refreshUserToGroupsMappings
-help
-
功能: 显示
dfsadmin
命令的帮助信息。 -
示例:
hdfs dfsadmin -help
HDFS 还有一些 HA 管理类命令,帮助管理员查看和管理 NameNode 的状态。以下是一些常用的相关命令及其功能和示例:
-getAllServiceState
-
功能: 查看 Hadoop HDFS 高可用(HA)配置中所有 NameNode 的状态。
-
示例:
hdfs haadmin -getAllServiceState
-getServiceState
-
使用方法:
hdfs haadmin -getServiceState <namenode_id>
-
功能: 查看指定 NameNode 的当前状态(
active
或standby
)。 -
示例:
hdfs haadmin -getServiceState nn1
-transitionToActive
-
使用方法:
hdfs haadmin -transitionToActive <namenode_id>
-
功能: 手动将指定的 NameNode 切换为
active
状态。 -
示例:
hdfs haadmin -transitionToActive nn2
-transitionToStandby
-
使用方法:
hdfs haadmin -transitionToStandby <namenode_id>
-
功能: 手动将指定的 NameNode 切换为
standby
状态。 -
示例:
hdfs haadmin -transitionToStandby nn1
-failover
-
使用方法:
hdfs haadmin -failover <active_namenode_id> <standby_namenode_id>
-
功能: 强制两个 NameNode 之间进行故障切换,将
standby
切换为active
,并将active
切换为standby
。 -
示例:
hdfs haadmin -failover nn1 nn2
-checkHealth
-
使用方法:
hdfs haadmin -checkHealth <namenode_id>
-
功能: 检查指定 NameNode 的健康状态。
-
示例:
hdfs haadmin -checkHealth nn1
HDFS编程
1、创建Maven工程并导入Jar包
在使用HDFS进行编程之前,需要创建一个Maven工程,并导入所需的依赖。以下是pom.xml
文件的配置示例:
<properties>
<hadoop.version>3.2.0</hadoop.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
</dependencies>
2、通过API操作HDFS
使用HDFS API进行文件操作,包括获取文件系统、文件上传、下载、创建目录、删除目录等操作。
import org.apache.hadoop.fs.*;
import org.apache.hadoop.conf.Configuration;
import org.junit.Test;
import java.io.IOException;
public class HDFSOperations {
@Test
public void hdfsOperations() throws IOException {
// 创建配置信息对象
Configuration configuration = new Configuration();
// 设置HDFS的默认文件系统
configuration.set("fs.defaultFS", "hdfs://127.0.0.1:9000");
configuration.set("dfs.replication", "3");
// 获取文件系统
FileSystem fs = FileSystem.get(configuration);
System.out.println(fs.toString());
// 获取文件列表
FileStatus[] statuses = fs.listStatus(new Path("/user"));
for (FileStatus status : statuses) {
System.out.println(status);
}
// 文件上传
Path sourcePath = new Path("D:\\data\\test1.txt");
Path targetPath = new Path("hdfs://127.0.0.1:9000/user/test");
fs.copyFromLocalFile(sourcePath, targetPath);
// 文件下载
Path targetPath1 = new Path("D:\\data\\test01.txt");
fs.copyToLocalFile(targetPath, targetPath1);
// 创建文件夹
Path newPath = new Path("hdfs://127.0.0.1:9000/user/soft863/263");
fs.mkdirs(newPath);
// 删除文件夹
fs.delete(newPath, true);
// 文件夹重新命名
Path newPath1 = new Path("hdfs://127.0.0.1:9000/user/soft863/bigdata");
fs.rename(newPath, newPath1);
// 获取文件列表
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("hdfs://127.0.0.1:9000/user"), true);
while (listFiles.hasNext()) {
LocatedFileStatus fileStatus = listFiles.next();
System.out.println(fileStatus.getPath().getName());
System.out.println(fileStatus.getBlockSize());
System.out.println(fileStatus.getPermission());
System.out.println(fileStatus.getLen());
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
for (BlockLocation bl : blockLocations) {
System.out.println("block-offset:" + bl.getOffset());
String[] hosts = bl.getHosts();
for (String host : hosts) {
System.out.println(host);
}
}
}
// 关闭文件系统
fs.close();
}
}
3、通过IO流操作HDFS
使用HDFS的输入输出流操作文件,可以实现文件的上传和下载。
import org.apache.hadoop.fs.*;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.IOUtils;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URI;
public class HDFSIOOperations {
@Test
public void putFileToHDFS() throws Exception {
// 创建配置信息对象
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://127.0.0.1:9000"), configuration);
// 创建输入流
FileInputStream inStream = new FileInputStream(new File("D:\\data\\test1.txt"));
// 获取输出路径
Path writePath = new Path("hdfs://127.0.0.1:9000/user/soft863/bigdata/test1.txt");
// 创建输出流
FSDataOutputStream outStream = fs.create(writePath);
// 流对接
try {
IOUtils.copyBytes(inStream, outStream, 4096, false);
} finally {
IOUtils.closeStream(inStream);
IOUtils.closeStream(outStream);
}
}
@Test
public void getFileFromHDFS() throws Exception {
// 创建配置信息对象
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://127.0.0.1:9000"), configuration);
// 获取读取文件路径
Path readPath = new Path("hdfs://127.0.0.1:9000/user/soft863/bigdata/test1.txt");
// 创建输入流
FSDataInputStream inStream = fs.open(readPath);
FileOutputStream fos = new FileOutputStream("D:\\data\\test02.txt");
// 流对接输出到控制台
try {
IOUtils.copyBytes(inStream, fos, 4096, false);
} finally {
IOUtils.closeStream(inStream);
IOUtils.closeStream(fos);
}
}
}
三、MapReduce详解
MapReduce概述
MapReduce 是一种用于处理大规模数据的编程模型,其核心思想是“分而治之”,通过并行计算来高效处理复杂任务。它适用于大规模数据处理场景,特别是那些数据之间没有计算依赖关系的情况。通过将复杂的任务拆分成若干个可以并行处理的小任务,MapReduce 能够显著提高数据处理的效率。
Map 阶段
- Map 负责“分”,即将复杂任务分解为若干个简单任务来并行处理。
- 前提是这些小任务之间没有依赖关系,可以独立计算。
- Map 阶段的输出通常是一组键值对
(k2, v2)
。
Reduce 阶段
- Reduce 负责“合”,即对 Map 阶段的结果进行全局汇总。
- Reduce 阶段接收来自 Map 阶段的键值对
(k2, v2)
,并将相同键(k2)
的值进行处理,输出新的键值对(k3, v3)
。
Hadoop MapReduce 的设计构思
- 如何处理大数据:分而治之
- MapReduce 通过将任务拆分为多个独立的小任务,实现并行计算。
- 不可分拆或有依赖关系的任务无法通过 MapReduce 进行并行计算。
- 构建抽象模型:Map 和 Reduce
- MapReduce 提供了高层的并行编程抽象模型,借鉴了函数式语言的思想。
map: (k1, v1) → [(k2, v2)]
,即对一组数据元素进行处理,并输出键值对。reduce: (k2, [v2]) → [(k3, v3)]
,即对 Map 阶段的中间结果进行进一步的汇总处理。
- 统一架构,隐藏系统层细节
- MapReduce 的亮点在于将“需要做什么”(what need to do)与“如何做”(how to do)分开。
- 程序员只需关注具体的计算问题,编写少量代码来实现业务逻辑,而具体的并行计算细节则由框架处理。
MapReduce架构
在分布式环境中运行的一个完整的 MapReduce 程序涉及三个主要的实例进程:
- MRAppMaster:负责整个程序的调度和状态协调。
- MapTask:负责处理 Map 阶段的数据处理流程。
- ReduceTask:负责处理 Reduce 阶段的数据处理流程。
MapReduce 设计构思
MapReduce 的分布式计算通常分为至少两个阶段:
- Map 阶段:多个
MapTask
并行执行,处理不同的数据片段,彼此独立。 - Reduce 阶段:多个
ReduceTask
并行执行,处理MapTask
输出的数据。ReduceTask
的输入依赖于所有MapTask
的输出。
MapReduce 编程模型只能包含一个 Map
阶段和一个 Reduce
阶段。如果业务逻辑非常复杂,则需要将多个 MapReduce 程序串行运行。
- MapTask:处理每一小部分数据并产生中间结果。
- ReduceTask:合并和处理所有
MapTask
生成的数据。
MapReduce框架
截至目前MapReduce框架分为两代
MapReduce 1.0 框架
- JobTracker
- Master 角色,管理所有作业。
- 将作业分解成一系列任务,并指派给
TaskTracker
。 - 负责作业和任务的监控与错误处理。
- TaskTrackers
- Slave 角色,运行
MapTask
和ReduceTask
。 - 与
JobTracker
交互,执行命令并汇报任务状态。
- Slave 角色,运行
- Map Task
- 负责解析每条数据记录。
- 将
map()
输出的数据写入本地磁盘(如果是 Map-only 作业,则直接写入 HDFS)。
- Reduce Task
- 从
MapTask
上远程读取输入数据。 - 对数据进行排序,并将数据按分组传递给用户编写的
reduce()
方法。
- 从
MapReduce 2.0 框架
- MRAppMaster功能类似于 1.0 中的
JobTracker
,但不负责资源管理。- 功能包括任务划分、资源申请、任务分配、任务状态监控和容错。
- 资源管理由
ResourceManager (RM)
处理,MRAppMaster
向RM
两次申请资源:- 第一次申请用于启动
MRAppMaster
,管理整个任务。 - 第二次申请用于启动
MapTask
和ReduceTask
所需的资源。
- 第一次申请用于启动
MapReduce计算流程:
MapReduce 程序运行流程分析
- 在 MapReduce 程序的输入目录中存放相应文件。
- 客户端在执行
submit()
方法前,获取待处理数据的信息,并根据集群配置形成任务分配规划。 - 客户端提交
job.split
、jar包
、job.xml
等文件给 YARN,YARN 的ResourceManager
启动MRAppMaster
。 MRAppMaster
启动后,根据任务描述信息,计算所需的MapTask
实例数量,并向集群申请资源启动相应数量的MapTask
进程。MapTask
利用用户指定的InputFormat
读取数据,形成输入的键值对(KV 对)。MapTask
将输入的 KV 对传递给用户定义的map()
方法进行处理。map()
方法处理完毕后,将 KV 对收集到MapTask
缓存(按分区存储)。- 缓存中的 KV 对按照键(K)进行排序后写入磁盘文件。
MRAppMaster
监控所有MapTask
完成后,启动相应数量的ReduceTask
进程,并告知ReduceTask
需要处理的数据分区。ReduceTask
启动后,从多个MapTask
运行所在的机器上获取输出文件,重新归并排序,并按键对数据分组,调用用户定义的reduce()
方法进行处理。ReduceTask
处理完毕后,调用用户指定的OutputFormat
将结果数据输出到外部存储。
MapTask详解
Map任务流程分析
在MapReduce的Map阶段,整个流程如下图所示:
简单概述:inputFile通过split被逻 辑切分为多个split文件,通过 Record按行读取内容给map(用 户自己实现的)进行处理,数据 被map处理结束之后交给 OutputCollector收集器,对其结 果key进行分区(默认使用hash分 区),然后写入buffer,每个map task都有一个内存缓冲区,存储 着map的输出结果,当缓冲区快 满的时候需要将缓冲区的数据以 一个临时文件的方式存放到磁盘, 当整个map task结束后再对磁盘 中这个map task产生的所有临时 文件做合并,生成最终的正式输 出文件,然后等待reduce task来 拉数据。
Map任务详解
-
InputFormat 和 Split 切分
首先,MapReduce框架使用InputFormat
(默认是TextInputFormat
)通过getSplits
方法对输入目录中的文件进行逻辑切分,得到splits
。每个split
对应启动一个MapTask
。默认情况下,split
与 HDFS 的block
是一对一的关系。 -
RecordReader 读取
在split
切分后,RecordReader
(默认是LineRecordReader
)逐行读取数据,以\n
作为分隔符。每次读取时返回一个Key-Value
对,其中Key
表示每行的首字符偏移值,Value
表示该行的文本内容。 -
调用用户自定义的 Mapper
读取到的数据进入用户自定义的Mapper
类,Mapper
的map()
方法对每行数据执行用户定义的逻辑处理。每次RecordReader
读取一行数据时都会调用一次map()
。 -
数据收集与分区
在map()
方法处理完每条数据后,通过context.write()
方法收集结果。数据收集时,MapReduce 会对其进行分区处理,默认使用HashPartitioner
。Partitioner
的作用是根据Key
或Value
及ReduceTask
的数量来决定当前数据应该交由哪个ReduceTask
处理。如果用户有自定义的Partitioner
需求,可以设置到Job
上。 -
缓冲区与溢写机制
收集的Key-Value
对会被写入内存中的缓冲区,该缓冲区称为环形缓冲区,旨在批量收集map
结果,减少磁盘 I/O 的影响。在写入缓冲区之前,Key
与Value
都会被序列化成字节数组。环形缓冲区是一个数组,存放着序列化后的Key
、Value
及其元数据信息,包括分区信息、Key
和Value
的起始位置以及Value
的长度。缓冲区的大小默认是100MB,当数据量大到接近缓冲区容量时,溢写机制(Spill)会将缓冲区中的数据写入磁盘生成临时文件。这是由单独的线程完成的,不影响继续向缓冲区写入
map
结果。溢写操作的触发阈值默认是缓冲区大小的80%,即80MB。 -
排序与 Combiner
当溢写线程启动后,会对缓冲区中的Key
进行排序,这是MapReduce框架的默认行为。排序后,如果Job
中配置了Combiner
,此时会对相同Key
的Key-Value
对的Value
进行合并,以减少溢写到磁盘的数据量。Combiner
是一种优化机制,但必须谨慎使用,确保其输出不会改变最终的Reduce
结果。常见的应用场景包括累加、求最大值等操作。 -
合并溢写文件
每次溢写会在磁盘上生成一个临时文件。如果MapTask
的输出非常大,可能会多次溢写生成多个临时文件。在MapTask
完成后,框架会将这些临时文件进行合并,生成一个最终的输出文件,并创建索引文件记录每个ReduceTask
所对应的数据偏移量。此时,Map
阶段正式结束。
源码
Split 分片
在 Hadoop MapReduce 中,split
是逻辑上的数据划分,用于优化 MapTask
的数据处理。以下是关于 split
划分的一些关键点:
-
Split 的定义与目的
split
是通过 Hadoop 中的InputFormat
接口中的getSplits()
方法生成的。其主要目的是为了让MapTask
更有效地获取数据。它并不直接影响物理数据的存储,而是影响数据的处理逻辑。 -
关键参数
- totalSize
整个MapReduce
作业的输入文件总大小。 - numSplits
由job.getNumMapTasks()
提供,即用户通过org.apache.hadoop.mapred.JobConf.setNumMapTasks(int n)
设置的值。这个值指定了期望的MapTask
数量,但实际的split
数量不一定等于这个值。这个参数主要作为split
数量的一个提示。 - goalSize
期望的split
大小,即totalSize / numSplits
。这是每个MapTask
处理的数据量的期望值,但实际情况可能有所不同。 - minSize
split
的最小大小,这可以通过以下方式设置:- 通过重写方法
protected void setMinSplitSize(long minSplitSize)
在InputFormat
子类中进行设置。通常为 1,但也可以根据特殊情况设置。 - 通过配置文件中的
mapreduce.input.fileinputformat.split.minsize
设置(默认值为 0)。最终的minSize
取这两者中的最大值。
- 通过重写方法
- totalSize
-
Split 计算公式
最终的
split
大小由以下公式计算:finalSplitSize = max(minSize, min(goalSize, blockSize))
- blockSize 是文件系统的块大小,用于确保
split
大小不会小于文件系统的块大小。
- blockSize 是文件系统的块大小,用于确保
计算细节
-
max(minSize, min(goalSize, blockSize))
的作用是:
- 保证
split
不会小于minSize
。 - 确保
split
大小接近goalSize
,但不会超过文件系统的块大小。
- 保证
排序、溢写(spill)到磁盘
分区
合并
对多个spill文件进行合并
ReduceTask详解
在MapReduce的Reduce阶段,整个流程如下图所示:
简单概述:ReduceTask大致分为copy、sort、reduce三个阶段,重点在前两个阶段。copy阶段包含一个eventFetcher来获取已完成的map列表,由Fetcher线程去copy数据,在此 过程中会启动两个merge线程,分别为inMemoryMerger和onDiskMerger,分别将内存中的数据merge到磁盘和将磁盘中的数据进行merge。待数据copy完成之后, copy阶段就完成了,开始进行sort阶段,sort阶段主要是执行finalMerge操作,纯粹 的sort阶段,完成之后就是reduce阶段,调用用户定义的reduce函数进行处理。
Reduce任务详解
在 Hadoop MapReduce 框架中,Reduce 任务主要负责对来自 Map 任务的中间数据进行整合和处理。整个 Reduce 任务可以分为以下几个关键阶段:
-
Copy 阶段
在这个阶段,Reduce 进程会启动多个数据复制线程(Fetcher),通过 HTTP 请求从各个
MapTask
获取属于该ReduceTask
的输出文件。这些文件包含了分配给该ReduceTask
的中间数据。这些复制线程是并行工作的,以提高数据传输效率。 -
Merge 阶段
在 Copy 阶段将数据复制到内存缓冲区后,Merge 阶段就会启动。Merge 阶段的目的是将来自不同
MapTask
的数据合并成一个统一的数据集。Merge 过程可以分为以下三种形式:- 内存到内存的 Merge:在数据刚被复制到内存时发生。小规模的 Merge 操作可以直接在内存中进行,不需要落盘。
- 内存到磁盘的 Merge:当内存缓冲区的数据量达到一定阈值时,系统会将这些数据从内存写到磁盘,并进行 Merge。这个过程类似于 Map 端的溢写(Spill)过程。如果你在作业配置中设置了 Combiner,那么它会在此时被启用,以减少数据量。
- 磁盘到磁盘的 Merge:当所有的
MapTask
数据都已经复制完毕后,系统会启动最后一次的磁盘到磁盘的 Merge 操作,将所有溢写到磁盘上的中间文件合并成一个最终的文件,为后续的 Reduce 处理做准备。
-
合并排序
在完成最终的 Merge 之后,Reduce 任务会对合并后的数据进行排序。排序是 MapReduce 模型的核心步骤,因为它保证了键值对是按键进行排序的,从而为后续的 Reduce 操作提供有序的输入。
-
Reduce 处理阶段
在这个阶段,系统会调用用户自定义的
reduce()
方法。对于每一个相同的键,reduce()
方法会被调用一次,并处理对应的所有值。reduce()
方法的输出可以是零个或多个键值对,这些输出结果将最终写入 HDFS(Hadoop Distributed File System)中。
总结
- Copy 阶段:从各个
MapTask
获取中间数据。 - Merge 阶段:将数据分批次合并,减少数据量。
- 合并排序:确保数据有序。
- Reduce 处理:对排序后的数据进行处理并输出结果。
整体流程
shuffle详解
Shuffle概念
在 MapReduce 框架中,shuffle
是连接 Map 阶段和 Reduce 阶段的关键过程。它涉及到数据的传递、排序、分区、分组以及合并操作。Shuffle 过程是决定 MapReduce 作业性能的核心因素之一,因为它直接影响到数据的传输效率和 Reduce 阶段的数据处理。
Shuffle概念图
Shuffle 详解
-
Collect 阶段
在 Map 阶段,每个
MapTask
处理输入数据后,会将结果输出到内存中的一个环形缓冲区。这个缓冲区默认大小为 100MB,保存着键值对(key/value)以及对应的分区信息(Partition)。当数据被写入缓冲区时,MapTask 并不会立即将这些数据写入磁盘,而是先在内存中积累。 -
Spill 阶段
当缓冲区中的数据量达到一定阈值时(例如,缓冲区使用量达到了 80%),系统会触发溢写(Spill)操作。溢写过程中,缓冲区中的数据会被写入磁盘。写入磁盘之前,系统会对数据进行排序,确保相同键的键值对是相邻的。如果配置了
Combiner
,系统还会在写入磁盘前对相同分区号和相同键的键值对进行合并,减少数据量。 -
Merge 阶段(Map 端)
在 Map 任务完成后,如果溢写操作多次发生,磁盘上会产生多个临时文件。此时,系统会将这些临时文件进行一次合并操作,确保每个
MapTask
最终只产生一个中间数据文件。这一步骤减少了后续 Reduce 任务的数据复制量。 -
Copy 阶段
当 Reduce 任务启动后,它会启动多个 Fetcher 线程,通过 HTTP 从各个
MapTask
节点复制属于它的数据。这些复制过来的数据默认会先存储在内存缓冲区中。当内存中的数据量达到一定阈值时,这些数据会被写入磁盘,以防止内存溢出。 -
Merge 阶段(Reduce 端)
在 Reduce 端的 Copy 阶段进行的同时,后台会启动两个线程,负责将内存中的数据和本地磁盘上的数据文件进行合并。这个过程与 Map 端的 Merge 类似,旨在减少数据文件的数量,为后续的 Reduce 处理做准备。
-
Sort 阶段
在数据合并的同时,Reduce 任务会对数据进行排序。排序确保了在 Reduce 阶段,具有相同键的键值对是相邻的,从而可以在一次 Reduce 调用中处理所有相同键的值。
Shuffle 中的缓冲区大小的影响
在 Shuffle 过程中,内存缓冲区的大小会直接影响 MapReduce 程序的执行效率。原则上,缓冲区越大,意味着可以在内存中保存更多的数据,从而减少磁盘 I/O 操作的次数,提升整体执行速度。然而,缓冲区过大会增加内存压力,导致内存溢出或系统性能下降。因此,合理配置缓冲区大小是优化 MapReduce 作业性能的关键。
MapReduce编程
Writable序列化
序列化是将内存中的对象转换为字节序列,以便进行存储(持久化)或网络传输;反序列化则是将字节序列或磁盘中的持久化数据转换回内存中的对象。Java 的序列化机制(Serializable
)相对重量级,带有很多附加信息,不适合在分布式环境中进行高效的数据传输。因此,Hadoop 提供了自己的一套轻量级序列化机制,即 Writable
。
序列化类型:
Java类型 | Hadoop Writable类型 |
---|---|
boolean | BooleanWritable |
byte | ByteWritable |
int | IntWritable |
float | FloatWritable |
long | LongWritable |
double | DoubleWritable |
string | Text |
map | MapWritable |
array | ArrayWritable |
MapReduce编程规范
在 MapReduce 编程模型中,用户编写的程序主要分为三个部分:Mapper
、Reducer
和 Driver
。其中:
- Mapper 阶段
- 用户自定义的
Mapper
类需要继承Mapper
父类。 Mapper
的输入数据是键值对(KV)的形式,类型可以自定义。- 业务逻辑写在
map()
方法中,该方法在每个MapTask
进程中会被调用一次。 map()
方法的输出数据也是键值对(KV)的形式,类型可以自定义。
- 用户自定义的
- Reducer 阶段
- 用户自定义的
Reducer
类需要继承Reducer
父类。 Reducer
的输入数据类型对应Mapper
的输出数据类型,都是键值对(KV)的形式。- 业务逻辑写在
reduce()
方法中。ReduceTask
进程会对每一组相同键的记录调用一次reduce()
方法,处理后的结果作为最终输出。
- 用户自定义的
- Driver 阶段
- 整个程序通过
Driver
进行提交。Driver
负责配置和提交Job
,包括设置Mapper
和Reducer
类、输入输出路径、输入输出格式等。
- 整个程序通过
范例:
//Mapper
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
/*
* map方法是提供给map task进程来调用的,map task进程是每读取一行文本来调用一次我们自定义的map方法
* map task在调用map方法时,传递的参数:
* 一行的起始偏移量LongWritable作为key
* 一行的文本内容Text作为value
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] words = line.split(" ");
for (String word : words) {
context.write(new Text(word), new IntWritable(1));
}
}
}
//Reducer
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int count = 0;
for (IntWritable value : values) {
count += value.get();
}
context.write(key, new IntWritable(count));
}
}
//Job提交
public class WordCountJobSubmitter {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
Job wordCountJob = Job.getInstance(conf);
// 重要:指定本job所在的jar包
wordCountJob.setJarByClass(WordCountJobSubmitter.class);
// 设置wordCountJob所用的mapper逻辑类为哪个类
wordCountJob.setMapperClass(WordCountMapper.class);
// 设置wordCountJob所用的reducer逻辑类为哪个类
wordCountJob.setReducerClass(WordCountReducer.class);
// 设置map阶段输出的kv数据类型
wordCountJob.setMapOutputKeyClass(Text.class);
wordCountJob.setMapOutputValueClass(IntWritable.class);
// 设置最终输出的kv数据类型
wordCountJob.setOutputKeyClass(Text.class);
wordCountJob.setOutputValueClass(IntWritable.class);
// 设置要处理的文本数据所存放的路径
FileInputFormat.setInputPaths(wordCountJob, new Path(args[0]));
FileOutputFormat.setOutputPath(wordCountJob, new Path(args[1]));
// 提交job给hadoop集群
wordCountJob.waitForCompletion(true);
}
}
//Mapper和Reducer无操作写法
public class MyMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
context.write(value, NullWritable.get());
}
}
public class MyReducer extends Reducer<Text, NullWritable, Text, NullWritable> {
@Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key, NullWritable.get());
}
}
//以上代码实现数据不做任何改动,进行传递
//数据清洗——自定义对象
public class LogBean {
private String remote_addr; // 记录客户端的ip地址
private String remote_user; // 记录客户端用户名称, 忽略属性"-"
private String time_local; // 记录访问时间与时区
private String request; // 记录请求的url与http协议
private String status; // 记录请求状态;成功是200
private String body_bytes_sent; // 记录发送给客户端文件主体内容大小
private String http_referer; // 用来记录从那个页面链接访问过来的
private String http_user_agent; // 记录客户浏览器的相关信息
private boolean valid = true; // 判断数据是否合法
public String getRemote_addr() {
return remote_addr;
}
public void setRemote_addr(String remote_addr) {
this.remote_addr = remote_addr;
}
public String getRemote_user() {
return remote_user;
}
public void setRemote_user(String remote_user) {
this.remote_user = remote_user;
}
public String getTime_local() {
return time_local;
}
public void setTime_local(String time_local) {
this.time_local = time_local;
}
public String getRequest() {
return request;
}
public void setRequest(String request) {
this.request = request;
}
public String getBody_bytes_sent() {
return body_bytes_sent;
}
public void setBody_bytes_sent(String body_bytes_sent) {
this.body_bytes_sent = body_bytes_sent;
}
public String getHttp_referer() {
return http_referer;
}
public void setHttp_referer(String http_referer) {
this.http_referer = http_referer;
}
public String getHttp_user_agent() {
return http_user_agent;
}
public void setHttp_user_agent(String http_user_agent) {
this.http_user_agent = http_user_agent;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.valid);
sb.append("\001").append(this.remote_addr);
sb.append("\001").append(this.remote_user);
sb.append("\001").append(this.time_local);
sb.append("\001").append(this.request);
sb.append("\001").append(this.status);
sb.append("\001").append(this.body_bytes_sent);
sb.append("\001").append(this.http_referer);
sb.append("\001").append(this.http_user_agent);
return sb.toString();
}
}
//数据清洗——LogMapper
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class LogMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
Text k = new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// 1 获取1行
String line = value.toString();
// 2 解析日志是否合法
LogBean bean = pressLog(line);
if (!bean.isValid()) {
return;
}
k.set(bean.toString());
// 3 输出
context.write(k, NullWritable.get());
}
// 解析日志
private LogBean pressLog(String line) {
LogBean logBean = new LogBean();
// 1 截取
String[] fields = line.split(" ");
if (fields.length > 11) {
// 2封装数据
logBean.setRemote_addr(fields[0]);
logBean.setRemote_user(fields[1]);
logBean.setTime_local(fields[3].substring(1));
logBean.setRequest(fields[6]);
logBean.setStatus(fields[8]);
logBean.setBody_bytes_sent(fields[9]);
logBean.setHttp_referer(fields[10]);
if (fields.length > 12) {
logBean.setHttp_user_agent(fields[11] + " " + fields[12]);
} else {
logBean.setHttp_user_agent(fields[11]);
}
// 大于400,HTTP错误
if (Integer.parseInt(logBean.getStatus()) >= 400) {
logBean.setValid(false);
}
} else {
logBean.setValid(false);
}
return logBean;
}
}
//数据清洗——任务提交
import org.apache.hadoop.conf.Configuration;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class LogDriver {
public static void main(String[] args) throws Exception {
// 1 获取job信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 加载jar包
job.setJarByClass(LogDriver.class);
// 3 关联map
job.setMapperClass(LogMapper.class);
// 4 设置最终输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// 5 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 6 提交
job.waitForCompletion(true);
}
}
//自定义输出一
/*
1、使用在Reducer中MultipleOutputs类,重写setup、cleanup方法
2、驱动类中禁止默认输出方式:
LazyOutputFormat.setOutputFormatClass (job, TextOutputFormat.class);
注意这种输出依然保留r-xxxxx
*/
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.output.MultipleOutputs;
import java.io.IOException;
public class AppleCountReducerCustomOutput extends Reducer<Text, IntWritable, Text, IntWritable> {
private MultipleOutputs<Text, IntWritable> multipleOutputs;
@Override
protected void setup(Context context) throws IOException, InterruptedException {
multipleOutputs = new MultipleOutputs<>(context);
}
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int num = 0;
for (IntWritable item : values) {
context.getCounter(key.toString(), "count").increment(1);
num += item.get();
}
String fileName = key.toString();
multipleOutputs.write(new Text(key), new IntWritable(num), fileName);
}
@Override
protected void cleanup(Context context) throws IOException, InterruptedException {
multipleOutputs.close();
}
}
//自定义输出二
/*
1、自定义输出类,继承TextOutputFormat,重写getDefaultWorkFile
2、驱动类中设置自定义输出方式:
LazyOutputFormat.setOutputFormatClass (job, MyFileOutputFormat.class);
MyFileOutputFormat.setOutputPath (job, new Path (args[1]));
*/
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.IOException;
public class MyFileOutputFormat extends TextOutputFormat<Text, Text> {
@Override
public Path getDefaultWorkFile(TaskAttemptContext context, String extension) throws IOException {
FileOutputCommitter fileOutputCommitter = (FileOutputCommitter) getOutputCommitter(context);
return new Path(fileOutputCommitter.getWorkPath(), getOutputName(context));
}
}
四、Yarn详解
Yarn概述
Apache Hadoop YARN(Yet Another Resource Negotiator,另一种资源协调者)是 Hadoop 集群中的资源管理系统模块。自 Hadoop 2.x 开始,YARN 被引入用于管理集群中的资源(主要是服务器的硬件资源,如 CPU 和内存)以及运行在 YARN 上的各种任务。简单来说,YARN 的主要功能是进行资源管理和任务调度。
Yarn 启动方式
-
启动 Yarn:
-
进入 Hadoop 文件夹后,运行以下命令:
./sbin/start-yarn.sh
-
-
关闭 Yarn:
-
进入 Hadoop 文件夹后,运行以下命令:
./sbin/stop-yarn.sh
-
-
启动历史日志服务:
-
启动 JobHistoryServer:
./sbin/mr-jobhistory-daemon.sh start historyserver
-
启动 TimelineServer:
./sbin/yarn-daemon.sh start timelineserver
-
Yarn 常用网址
- YARN 集群的监控管理界面:
- JobHistoryServer 查看界面:
Yarn 帮助文档
-
官方文档:
-
本地文档:
-
如果你已经下载了 Hadoop包,可以在以下路径找到本地文档:
hadoop-3.2.0/share/doc/hadoop/hadoop-yarn/
-
Yarn 架构
Yarn 的架构由 ResourceManager 和 NodeManager 两部分组成,它们共同负责资源管理和任务调度。以下是 Yarn 架构的详细内容:
1. ResourceManager
ResourceManager 是 Yarn 的核心组件,负责整个集群的资源管理和任务调度。它的主要功能包括:
- 处理客户端请求:接收和处理用户提交的任务请求。
- 启动/监控 ApplicationMaster:为每个应用启动一个 ApplicationMaster,并监控其运行状态。
- 监控 NodeManager:监控集群中各个 NodeManager 的状态。
- 资源分配与调度:通过调度器(如 FairScheduler 或 CapacityScheduler)管理集群资源的分配和任务的调度。
ResourceManager 进一步分为两个子组件:
- Scheduler:负责资源的分配和任务的监控,常用的调度器包括 FairScheduler 和 CapacityScheduler。
- ApplicationsManager:负责接收用户提交的任务,协调启动第一个 ApplicationMaster 的 Container,并在必要时重新启动失败的 ApplicationMaster。
2. NodeManager
NodeManager 是 Yarn 集群中每个节点的代理,负责本节点的资源管理和任务执行。它的主要功能包括:
- 资源管理和任务管理:管理节点上的资源(CPU、内存、磁盘、网络)和任务执行。
- 接收并处理命令:
- 来自 ResourceManager 的命令:如启动、停止任务。
- 来自 ApplicationMaster 的命令:如资源申请、任务调度。
- 管理 Container:Container 是 Yarn 中资源的分配单位,NodeManager 负责管理和监控 Container。
- 资源和状态汇报:定期向 ResourceManager 汇报节点资源使用情况和 Container 的运行状态。
3. ApplicationMaster (AM)
当有新的任务提交到 ResourceManager 时,ResourceManager 会在某个节点的 NodeManager 上启动一个 ApplicationMaster 进程。ApplicationMaster 是每个应用的管理者,主要负责:
- 资源申请:向 ResourceManager 申请资源。
- 任务调度:与 NodeManager 交互,分配任务执行的资源。
- 任务监控:监控任务的执行状态和生命周期。
4. Container
Container 是 Yarn 中的资源分配单位,表示一组资源,如 CPU、内存等。当 ApplicationMaster 启动后,会向 ResourceManager 请求资源,ResourceManager 会将资源分配给 ApplicationMaster,这些资源就是一个个的 Container。
Container 的主要作用:
- 任务运行资源:包括节点、内存、CPU等资源。
- 任务运行环境的抽象:提供任务执行所需的资源环境。
5. JobHistoryServer
JobHistoryServer 是 Yarn 提供的一个服务,用于查看已经完成任务的历史日志记录。用户可以通过启动 JobHistoryServer 来查看任务的详细日志信息。
- 启动界面:
http://服务器地址:19888/jobhistory
6. TimeLineServer
TimeLineServer 是为了解决在 Hadoop 2.4 版本之前,监控任务执行只能通过 MR 的 Job History Server 查看的问题。随着在 Yarn 上集成的计算框架增多(如 Spark、Tez 等),需要一个更加通用的任务监控工具,因此开发了 TimeLineServer。
TimeLineServer 的主要职责:
- 收集和检索信息:收集和检索应用程序或框架的相关信息。
- 信息传输:应用程序开发者可以通过 TimelineClient 在 ApplicationMaster 或者应用程序的 Container 中将信息发送给 TimeLineServer。
- 信息查询:这些信息可以通过 REST APIs 在具体应用程序或执行框架的 UI 界面中查询。
Yarn读写流程
1. 提交应用程序
用户向YARN提交应用程序,其中包括ApplicationMaster程序、启动ApplicationMaster的命令、用户程序等。这个过程涉及将所有必要的资源和任务定义提交给YARN集群。
2. 启动ApplicationMaster
ResourceManager为应用程序分配第一个Container,并与相应的NodeManager通信,要求其在该Container中启动应用程序的ApplicationMaster。ApplicationMaster是负责管理应用程序生命周期的组件。
3. 注册ApplicationMaster
ApplicationMaster启动后,首先向ResourceManager注册。通过注册,ResourceManager可以在用户查询时提供应用程序的运行状态。
4. 申请资源
ApplicationMaster通过RPC协议以轮询的方式向ResourceManager申请和领取资源。它会根据应用程序的需求来请求更多的Container以运行应用程序的各个任务。
5. 分配资源并启动任务
一旦ApplicationMaster成功申请到资源,它将与相应的NodeManager通信,要求其启动任务。NodeManager是负责实际执行任务的YARN组件。
6. 任务启动
NodeManager为任务设置好运行环境,包括配置环境变量、加载JAR包、二进制程序等。任务的启动命令会被写入一个脚本中,并通过运行该脚本启动任务。
7. 任务状态监控
各个任务通过某个RPC协议向ApplicationMaster汇报自己的状态和进度。ApplicationMaster负责监控所有任务的执行状态,以便在任务失败时能够重新启动任务。这保证了应用程序的稳定运行。
8. 完成与注销
当应用程序的所有任务完成后,ApplicationMaster向ResourceManager注销,并自行关闭。此时,应用程序的生命周期结束,所有占用的资源都会被释放。
Yarn 调度器
在 Yarn 框架中,调度器是至关重要的组件,它决定了多个应用在集群中如何有效地共享资源。Yarn 提供了多种调度策略,能够根据不同需求实现资源的合理分配和任务的有序执行。以下是 Yarn 提供的几种主要调度器:
1. FIFO Scheduler (队列调度器)
FIFO(First In, First Out)Scheduler 是最简单的一种调度器,其原理是按照任务提交的顺序排成一个队列,资源分配时优先满足队列最前端的任务。尽管简单易理解,FIFO Scheduler 的缺点是它不适用于共享集群。大的任务可能会占用所有资源,导致其它任务被阻塞。因此,在共享集群中,更常使用 Capacity Scheduler 或 Fair Scheduler。
2. Capacity Scheduler(容量调度器)
Capacity Scheduler 是 Apache Hadoop 的默认调度器,它允许多租户共享整个集群资源。每个组织(租户)可以通过分配专门的队列获得集群的一部分计算能力。通过配置多个队列,可以让多个租户在同一个集群中运行任务。此外,在每个队列内部分配的资源调度通常是采用 FIFO 策略。
-
启用 CapacityScheduler:
<property> <name>yarn.resourcemanager.scheduler.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value> </property>
-
更多信息: 参考 CapacityScheduler 文档。
3. Fair Scheduler(公平调度器)
该调度器是CDH版本的hadoop默认使用的调度器,Fair Scheduler 的目标是为所有应用程序分配公平的资源。其设计思想是将资源平均分配给每个应用,即基于时间的等额资源分配策略。当只有一个应用运行时,它会使用分配给它的所有资源;当有新的应用提交时,资源会在多个应用间公平分配。
-
示例: 假设用户 A 和 B 各有一个队列,当 A 提交任务而 B 没有任务时,A 会获得全部资源。当 B 后来提交任务,资源将重新分配,最终两者平等共享资源。如果 B 再次提交任务,这两个任务会共享 B 队列的资源,A 则继续使用自己队列的一半资源。
-
启用 FairScheduler:
<property> <name>yarn.resourcemanager.scheduler.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler</value> </property>
-
更多信息: 参考 FairScheduler 文档。
总结
- FIFO Scheduler:简单易用,但不适合共享集群。
- Capacity Scheduler:支持多租户,通过队列分配资源,默认用于 Apache Hadoop。
- Fair Scheduler:基于公平原则分配资源,适合需要动态平衡资源的场景,默认用于 CDH 版本的 Hadoop。
根据具体的应用需求和集群环境,可以选择合适的调度器,以确保资源的高效利用和任务的顺利执行。
Yarn常见配置
在配置 Hadoop Yarn 时,需要修改 yarn-site.xml
文件中的一些关键参数,以满足集群的具体需求。
- yarn.scheduler.minimum-allocation-mb:
- 值: 1024
- 说明: 定义了每个应用程序
Container
能够分配的最小内存(以 MB 为单位)。通常用于防止单个任务占用过多资源。
- yarn.scheduler.maximum-allocation-mb:
- 值: 8192
- 说明: 定义了每个应用程序
Container
能够分配的最大内存(以 MB 为单位)。这是限制Container
最大资源消耗的参数。
- yarn.scheduler.minimum-allocation-vcores:
- 值: 1
- 说明: 指定了每个
Container
最少分配的虚拟 CPU 核心数。
- yarn.scheduler.maximum-allocation-vcores:
- 值: 4
- 说明: 指定了每个
Container
可以分配的最大虚拟 CPU 核心数。
- yarn.nodemanager.resource.memory-mb:
- 值: 8192
- 说明:
NodeManager
可用于分配给Container
的最大内存大小(以 MB 为单位)。这是一个全局限制,确保节点上不会超额使用内存。
- mapreduce.framework.name:
- 值:
yarn
- 说明: 指定 MapReduce 程序运行在 Yarn 框架上。
- 值:
- yarn.nodemanager.aux-services:
- 值:
mapreduce_shuffle
- 说明: 配置节点上可以运行的附加服务,例如 MapReduce Shuffle 服务。
- 值:
- yarn.resourcemanager.hostname:
- 说明: 指定 Yarn 的
ResourceManager
地址,确保客户端能够与ResourceManager
正确通信。
- 说明: 指定 Yarn 的
- yarn.log-aggregation-enable:
- 说明: 启用日志聚合功能,当应用程序运行完成后,将日志上传到 HDFS,以便集中管理和存储。
- yarn.log-aggregation.retain-seconds:
- 说明: 定义日志在 HDFS 中的保留时间(以秒为单位),默认情况下日志保留时间为 7 天。
配置示例
<configuration>
<property>
<name>yarn.scheduler.minimum-allocation-mb</name>
<value>1024</value>
</property>
<property>
<name>yarn.scheduler.maximum-allocation-mb</name>
<value>8192</value>
</property>
<property>
<name>yarn.scheduler.minimum-allocation-vcores</name>
<value>1</value>
</property>
<property>
<name>yarn.scheduler.maximum-allocation-vcores</name>
<value>4</value>
</property>
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>8192</value>
</property>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>your-resourcemanager-hostname</value>
</property>
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value> <!-- 7 days in seconds -->
</property>
</configuration>
Yarn 常用命令
-
-list
列出所有的 application 信息。-
用法示例:
yarn application -list
-
可选参数:
-
-appStates
用于筛选不同状态的 application,多个状态使用逗号分隔。
yarn application -list -appStates RUNNING,ACCEPTED
-
-appTypes
用于筛选不同类型的 application,多个类型使用逗号分隔。
yarn application -list -appTypes MAPREDUCE
-
-
说明: 该命令可以通过结合
-appStates
和-appTypes
来过滤出特定的应用程序列表。
-
-
-kill
杀死一个指定的 application。-
用法示例:
yarn application -kill application_1526100291229_206393
-
说明: 使用这个命令可以通过 Application ID 强制停止一个正在运行的任务。
-
-
-status
查看指定 application 的状态信息。-
用法示例:
yarn application -status application_1526100291229_206393
-
说明: 该命令可以详细显示应用程序的状态、资源使用情况和任务进度。
-
-
-appStates
与-list
一起使用,用来筛选不同状态的 application。-
可用状态:
ALL
: 显示所有状态的应用程序。NEW
,NEW_SAVING
,SUBMITTED
,ACCEPTED
,RUNNING
,FINISHED
,FAILED
,KILLED
。
-
用法示例:
yarn application -list -appStates RUNNING
-
说明: 这个参数通常与
-list
命令结合使用,按状态筛选应用程序。
-
-
-appTypes
与-list
一起使用,用来筛选不同类型的 application。-
用法示例:
yarn application -list -appTypes MAPREDUCE
-
说明: 常用类型包括
MAPREDUCE
,SPARK
等。
-
-
-logs
获取应用程序的日志。-
用法示例:
yarn logs -applicationId application_1526100291229_206393
-
说明: 该命令用于从运行的应用程序中获取日志信息,帮助排查问题。
-
-
-movetoqueue
将应用程序移动到另一个队列。-
用法示例:
yarn application -movetoqueue -appId application_1526100291229_206393 -queue newQueue
-
说明: 允许在运行时将应用程序移动到不同的调度队列中。
-
-
-failapplicationattempt
强制失败当前应用程序的尝试。-
用法示例:
yarn applicationattempt -failattempt attempt_1526100291229_0001_000002
-
说明: 用于对当前正在尝试的应用程序进行失败处理,这通常用于手动触发故障切换。
-
-
-applicationattemptstatus
查看应用程序尝试的状态。-
用法示例:
yarn applicationattempt -status attempt_1526100291229_0001_000002
-
说明: 该命令提供应用程序的当前尝试状态和详细信息。
-
-
-containerstatus
查看某个容器的状态。-
用法示例:
yarn container -status container_1526100291229_0001_01_000002
-
说明: 用于检查特定容器的状态、资源使用情况和运行时间。
-
-
-node
列出或查看节点信息。-
用法示例:
yarn node -list yarn node -status nodemanager1.example.com
-
说明: 可以查看所有节点的列表或获取特定节点的详细状态。
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?