写在前面

本篇来深入学习HDFS的运行原理,主要包括HDFS文件的读写流程、副本机制和文件数据的负载均衡和机架感知。

HDFS文件读写流程

HDFS文件读流程

首先学习HDFS文件的读流程,主要是客户端读取数据,其流程如下所示:

(1)客户端通过调用FileSystem的open方法获取需要读取的数据文件,对于HDFS来说这个FileSystem就是DistributeFileSystem;
(2)DistributeFileSystem通过RPC来调用NameNode,进而获取到需要读取的数据文件对应的Block存储在哪些DataNode上;
(3)客户端会先到最佳位置(所谓最佳位置其实就是离它最近的)的DataNode上调用FSDataInputStream的read方法,通过反复调用read方法,就可以将数据从DataNode传递到客户端;
(4)当读取完所有的数据之后,FSDataInputStream就会关闭与DataNode的连接,然后寻找下一块的最佳位置,也就是说对于客户端而言,它只需要读取连续的流;
(5)一旦客户端完成读取操作后,就对FSDataInputStream调用close方法来完成资源的关闭操作。

HDFS文件写流程

同样对于HDFS来说,写数据的过程也是由客户端来完成的,其流程如下所示:

(1)客户端通过调用DistributeFileSystem的create方法来创建一个文件;
(2)DistributeFileSystem会对NameNode发起RPC请求,在文件系统的名字空间中创建一个新的文件,此时会进行各种检查,如检查待创建的文件是否已经存在,如果该文件不存在,那么NameNode就会为该文件创建一条元数据记录;
(3)客户端调用FSDataOutputStream的write方法将数据写入到一个内部队列中。假设副本系数为3,那么会将队列中的数据写到3个副本对应存储的DataNode上;
(4)FSDataOutputStream内部维护了一个确认队列,当接收到所有DataNode确认写完的消息后,数据才会从确认队列中删除;
(5)当客户端完成数据的写入后,会对数据流调用close方法来关闭相关资源。

HDFS文件读写小结

综上可知,客户端其实都是先通过调用DistributeFileSystem的方法来进行文件操作,之后通过发起RPC请求来对NameNode进行操作,之后就是调用FSDataInputStream/FSDataOutputStream流来对数据进行读取/写入操作,最后都会调用数据流的close方法来关闭相关资源。

HDFS副本机制

前面多次提到副本系数,那么接下来就仔细研究HDFS的副本机制。

我们知道HDFS上的文件对应的Block保存有多个副本且提供容错机制,也就是说如果此时部分副本丢失或者发送宕机时,HDFS可以对文件进行自动恢复。HDFS默认保存3个副本。

HDFS副本摆放机制如下所示:

副本摆放策略

这里就以HDFS默认保存3个副本为例来学习副本摆放策略:
第一个副本:放在上传文件的DataNode上(图中深色的是客户端);注意如果是集群外提交,那么会随机挑选一个磁盘不太慢、CPU不太忙的节点;
第二个副本:放在与第一个副本不同机架的节点上;
第三个副本:放在与第二个副本相同机架的不同节点上;
假设存在第n个副本,那么将随机的放在节点中。

副本系数

(1)当开发者上传文件A到HDFS中时,此时HDFS中副本系数就就决定了这个文件A的块副本数,之后无论怎么修改系统的副本系数,这个文件A的副本数都不会变化。

也就是说,上传到HDFS系统的文件副本数是由当时系统的副本系数来决定,之后副本系数的修改不会对它产生影响。

(2)在上传文件时,开发者可以指定副本系数,dfs.replication是客户端的属性,如果用户不指定具体的replication副本系数时,那么系统就使用默认值。

请注意文件上传之后,其备份数就已经确定,此时再修改dfs.replication参数值不会影响之前文件的副本数,也不影响后面指定副本数的文件,仅仅只影响使用默认副本数的文件。

(3)再次强调dfs.replication是客户端的属性,也就是说如果客户端未设置,系统才会去读取配置文件中的数据。

(4)还有一个比较容易出错的地方就是用户明明在hdfs-site.xml配置文件中设置了dfs.replication=1,但是此时块的备份数依旧还是3,而不是1,那可能是因为用户没有将hdfs-site.xml配置文件加入到工程的classpath中,这样就导致程序在运行时读取的依旧还是hdfs-default.xml配置文件中默认配置的dfs.replication=3,这个问题很容易发生,因此需要格外引起注意。

HDFS负载均衡

HDFS架构支持数据负载均衡策略,也就是说当某个DataNode上的空闲空间低于特定的临界点,那么按照数据均衡策略,系统就会自动将数据从这个DataNode移动到其他空闲的DataNode上。

当对某个文件的请求突然增加时,系统也有可能启动一个计划来创建该文件新的副本,同时重新平衡集群中的其他数据。当HDFS负载不均衡时,需要对HDFS进行数据的负载均衡调整,即对各个节点机器上数据的存储分布进行调整,进而让数据均匀地分布在各个DataNode上,以均衡IO性能、平衡IO负载、平均数据、平衡集群防止发生热点。

在Hadoop的$HADOOP_HOME/sbin目录下有一个名为start-balancer.sh的脚本,开发者可以通过运行这个脚本来启动HDFS的数据均衡服务Balancer,相应的启动命令为:

1
2
3
[root@master sbin]# ./start-balancer.sh
//或者
[hadoop@master ~]$ $HADOOP_HOME/sbin/start-balancer.sh

请注意这个命令后面可以跟几个参数:
(1)-threshold,默认值为10,取值范围0~100;该参数是判断集群是否平衡的阈值。理论上说该参数值越小,则整个集群就越平衡。
(2)dfs.balance.bindwidthPerSec,默认值为1048576(1M/s);该参数表示Balancer运行时允许占用的带宽。

接下来通过结合上述两个命令参数来介绍几个例子:
(a)启动数据平衡,默认阈值为10%,此时命令为:

1
[hadoop@master ~]$ $HADOOP_HOME/sbin/start-balancer.sh

(b)启动数据平衡,阈值为5%,此时命令为:

1
[hadoop@master ~]$ $HADOOP_HOME/sbin/start-balancer.sh -threshold 5

(c)停止数据平衡,此时命令为:

1
[hadoop@master ~]$ $HADOOP_HOME/sbin/stop-balancer.sh

而关于设置数据均衡占用的网络带宽,用户可以在hdfs-site.xml文件中进行设置:

1
2
3
4
5
6
<configuration>
<property>
<name>dfs.balance.bindwidthPerSec</name>
<value>1048576</value>
</property>
</configuration>

HDFS机架感知

通常大型的Hadoop集群是以机架的形式来组织的,同一个机架上的不同节点间的网络状况比不同机架之间的网络状况好很多,但是NameNode为了提高容错性,会将数据块副本保存在不同的机架上。

HDFS不能自动判断集群中各个DataNode的网络拓扑情况,但是Hadoop允许集群管理员通过配置dfs.network.script参数来确定节点所处的机架。配置文件提供了ip到rackid的翻译,这样NameNode通过这个配置可以知道集群中各个DataNode机器的rackid。请注意,如果topology.script.file.name没有设定,那么每个ip都会被翻译成/default-rack

机架感知如下图所示:

上图中的D和R代表交换机,H代表DataNode,那么H1的rackid=/D1/R1/H1,其实这个rackid可以通过topology.script.file.name参数来配置,这样就能计算出任意两个DataNode之间的距离。

举个例子,如下所示:
(1)distance(/D1/R1/H1,/D1/R1/H1)=0,表示相同的DataNode;
(2)distance(/D1/R1/H1,/D1/R1/H2)=2,表示同一rack下的不同DataNode;
(3)distance(/D1/R1/H1,/D1/R1/H4)=4,表示同一IDC下的不同DataNode;
(4)distance(/D1/R1/H1,/D1/R1/H7)=6,表示不同IDC下的DataNode;

关于HDFS机架感知还有以下两点需要注意:
(1)当没有配置机架信息时,那么所有的机器都在同一个默认的,名为/default-rack的机架下。此种情况下的任何一台DataNode机器,不管是物理上是否属于同一个机架,它都会被认为是在同一个机架下。
(2)用户一旦配置了topology.script.file.name,那么就可以按照网络拓扑结构来寻找DataNode。通常我们都会将topology.script.file.name这个配置选项的value指定为一个可执行程序,这样就便于后续执行。

这样关于HDFS运行原理的深度学习就到此为止,后续学习其他内容。