HDFS架构核心
一、简介
HDFS(Hadoop Distributed File System) 是 Apache Hadoop 项目的一个子项目,设计目的是用于存储海量(例如:TB和PB)文件数据,支持高吞吐读写文件并且高度容错。HDFS将多台普通廉价机器组成分布式集群形成分布式文件系统,提供统一的访问接口,用户可以像访问普通文件系统一样来使用HDFS访问文件。
HDFS有如下特点:
- HDFS适合处理大规模数据,如:TB和PB,可以处理百万规模以上的文件数量,使用场景是一次写入、多次读取场景。
- HDFS将文件线性按字节切分成多个block块进行存储,每个block块默认128M。
- 每个block块默认有3个副本,提高容错性,如果一个副本丢失不可用,后续可以自动恢复。
- HDFS适合大文件写入,不适合大量小文件写入,因为小文件多NameNode要使用更多内存来维护存储文件目录和block信息。此外,读取大量小文件时,文件寻址时间要大于文件读取时间,违反HDFS设计目标。
- HDFS不支持并发写入数据,一个文件只能有一个写,不能多个线程同时写。
- HDFS数据写入后不支持修改,只支持append追加。
二、架构
2.1 NameNode
NameNode就是主从架构中的Master,是HDFS中的管理者。HDFS中数据文件分布式存储在各个DataNode节点上,NameNode维护和管理文件系统元数据(空间目录树结构、文件、Block信息、访问权限),随着存储文件的增多,NameNode上存储的信息越来越多,NameNode主要通过两个组件实现元数据管理:fsimage(命名空间镜像文件)和editslog(编辑日志)。
- fsimage:HDFS文件系统元数据的镜像文件,其中包含了HDFS文件系统的所有目录和文件相关信息元数据。
- edits:用户操作HDFS的编辑日志文件,存放HDFS文件系统的所有操作事件,文件的所有写操作会被记录到Edits文件中。
fsimage中存储了当前HDFS中文件属性(文件名称、路径、权限关系、副本数、修改、访问时间等),当HDFS启动后,首先会将磁盘中的fsimage加载到内存中,这样可以保证用户HDFS的高效和低延迟。需要注意,fsimage中不记录每个block所在的DataNode信息,这些信息在每次HDFS启动时从DataNode重建,之后DataNode会周期性的通过心跳向NameNode报告block信息。
在NameNode运行期间,客户端对HDFS的操作(文件或目录的创建、重命名、删除)日志都会保存在edits文件中,edits文件保存在磁盘中。当NameNode重启时,会将fsimage内容映射到内存中,然后再一条条执行edits文件中的操作就可以恢复到NameNode重启前的状态,做到不丢失数据。
总结,NameNode作用如下:
- 完全基于内存存储文件元数据、目录结构、文件block的映射信息。
- 提供文件元数据持久化/管理方案。
- 提供副本放置策略。
- 处理客户端读写请求。
2.2 SecondaryNameNode
随着操作HDFS的数据变多,久而久之就会造成edits文件变的很大,如果namenode重启后再一条条执行edits日志恢复状态就需要很长时间,导致重启速度慢,所以在NameNode运行的时候就需要将editslog和fsimage定期合并。这个合并操作就由SecondaryNameNode负责。
所以SecondaryNameNode作用就是辅助NameNode定期合并fsimage和editslog,并将合并后的fsimage推送给NameNode。关于详细的fsimage和editslog合并过程参照后续小节。
2.3 DataNode
DataNode是主从架构中的Slave,DataNode存储文件block块,Block在DataNode上以文件形式存储在磁盘上,包括2个文件,一个是数据文件本身,一个是元数据(包括block长度、block校验和、时间戳)。当DataNode启动后会向NameNode进行注册,并汇报block列表信息,后续会周期性(参数dfs.blockreport.intervalMsec决定,默认6小时)向NameNode上报所有的块信息。同时,DataNode会每隔3秒与NameNode保持心跳,如果超过10分钟NameNode没有收到某个DataNode的心跳,则认为该节点不可用。
总结,DataNode作用如下:
- 基于本地磁盘存储block数据块。
- 保存block的校验和数据保证block的可靠性。
- 与NameNode保持心跳并汇报block列表信息。
2.4 Client
Client是操作HDFS的客户端,作用如下:
- 与NameNode交互,获取文件block位置信息。
- 与DataNode交互,读写文件block数据。
- 文件上传时,负责文件切分成block并上传。
- 可以通过client访问HDFS进行文件操作或管理HDFS。
三、fsimage和editslog合并
3.1 合并流程
前面小节中介绍到HDFS 中NameNode管理通过fsimage和editslog来管理集群元数据,SecondaryNameNode会负责定期合并fsimage和editslog,以保证HDFS集群重启后快速恢复到之前状态。
下图是SecondaryNameNode进行fsimage和editslog合并整个流程图:
- 当HDFS集群首次启动会在NameNode上创建空的fsimage,对HDFS的操作会记录到edits文件中。
- 当开始进行editslog和fsimage合并时,SecondaryNameNode请求namenode生成新的editslog文件并向其中写日志。
- SecondaryNameNode通过HTTP GET的方式从NameNode下载fsimage和edits文件到本地。
- SecondaryNameNode将fsimage加载到自己的内存,并根据editslog更新内存中的fsimage信息,然后将更新完毕之后的fsimage写到磁盘上。
- SecondaryNameNode通过HTTP PUT将新的fsimage文件发送到NameNode,NameNode将该文件保存为.ckpt的临时文件备用。
- NameNode重命名该临时文件并准备使用,此时NameNode拥有一个新的fsimage文件和一个新的很小的editslog文件(可能不是空的,因为在SecondaryNameNode合并期间可能对元数据进行了读写操作)。
- 后续SecondaryNameNode会按照以上步骤周期性进行editslog和fsimage的合并。
3.2 合并时机
默认情况下,SecondaryNameNode每隔1小时执行edits和fsimage合并,通过参数“dfs.namenode.checkpoint.period”进行控制,默认该参数为3600s,即:1小时。
HDFS还会每分钟进行NameNode操作事务数量检查,如果editslog存储的事务(即操作数)到了1000000个也会进行editslog和fsimage的合并。每分钟检查操作事务参数通过dfs.namenode.checkpoint.check.period设置,默认60s,editslog操作数控制参数为dfs.namenode.checkpoint.txns,默认1000000。
四、安全模式
4.1 安全模式工作流程
当HDFS启动后,根据前面知识的学习,启动后工作流程大致如下:
- 启动NameNode,NameNode加载fsimage到内存,对内存数据执行edits log日志中的事务操作。
- 文件系统元数据内存镜像加载完毕,进行fsimage和edits log日志的合并,并创建新的fsimage文件和一个空的edits log日志文件。
- NameNode等待DataNode上传block列表信息,直到副本数满足最小副本条件,这个过程NameNode处于安全模式,最小副本条件指整个文件系统中有99.9%的block达到了最小副本数(默认值是1,可设置)。
- 当满足了最小副本条件,再过30秒,NameNode就会退出安全模式。
在NameNode安全模式(safemode)下,操作HDFS有如下特点:
- 对文件系统元数据进行只读操作。
- 当文件的所有block信息具备的情况下,对文件进行只读操作。不允许进行文件修改(写,删除或重命名文件)。
4.2 注意事项
- NameNode不会持久化block位置信息,DataNode保有各自存储的block列表信息。正常操作时,NameNode在内存中有一个blocks位置的映射信息(所有文件的所有文件块的位置映射信息)。
- NameNode在安全模式,DataNode需要上传block列表信息到NameNode。
- 在安全模式NameNode不会要求DataNode复制或删除block。
- 新格式化的HDFS不进入安全模式,因为DataNode压根就没有block。
4.3 配置信息
属性名称 | 类型 | 默认值 | 描述 |
dfs.namenode.replication.min | Int | 1 | 写文件成功的最小副本数 |
dfs.namenode.safemode.threshold-pct | float | 0.999 | 系统中block达到了最小副本数的比例,之后NameNode会退出安全模式。 |
dfs.namenode.safemode.extension | Int | 30000ms | 系统中block达到了最小副本数的比例,之后NameNode会退出安全模式。 |
4.4 命令操作
通过以下命令查看namenode是否处于安全模式:
hdfs dfsadmin -safemode get
HDFS的前端webUI页面也可以查看NameNode是否处于安全模式,有时候我们希望等待安全模式退出,之后进行文件的读写操作,尤其是在脚本中,此时可以执行命令:
hdfs dfsadmin -safemode wait
当然,以上这个命令不会经常使用,更多的是集群正常启动后会自动退出安全模式。管理员有权在任何时间让namenode进入或退出安全模式,进入安全模式命令如下:
hdfs dfsadmin -safemode enter
以上命令可以让namenode一直处于安全模式,离开安全模式命令如下:
hdfs dfsadmin -safemode leave
五、Block及副本存放策略
5.1 block块
HDFS存储文件数据时会将文件切分成block,block大小由参数dfs.blocksize决定,在Hadoop1.x中block大小默认为64M ,在Hadoop2.x/3.x中每个block默认大小为128M。
HDFS中块大小不能设置太大,也不能设置太小。如果块设置太大会导致读取block时从磁盘传输数据的时间明显大于寻址时间,导致程序处理数据时变的非常慢;如果块设置过小,大量的块会占用NameNode大量内存来存储元数据,而NameNode内存有限,另一方面,文件块过小会导致寻址时间增大,导致程序一直在找block的开始位置。
在HDFS中平均查找block的寻址时间为10ms,经过测试,block文件寻址时间为block传输时间的1%时机器性能最佳,即block传输时间为1s(10ms/0.01=1000ms=1s)时机器性能最佳,目前磁盘的传输速率普遍为100MB/s,计算出最佳block大小为100M(100MB/s*1s=100MB),由于在计算机领域,计算机使用的是二进制系统,而2的幂次方在二进制系统中具有简单而高效的表示方式,使用这些大小的数据更为方便,如:2的7次方是128,2的8次方是256,以此类推。所以block没有设置为100M,而是设置为了128M。所以HDFS中块大小的设置主要取决于磁盘的传输速率,在实际生产中,如果磁盘传输速率为200MB/s时,一般设置block的大小为256M,如果磁盘传输速率为400MB/s时,一般设置block大小为512M。
此外,需要注意如果一个文件本身是1KB,上传到HDFS中就对应1个Block,该Block大小实际占用1KB,即:Block大小默认128M表示存储一份数据的分片上限大小,一个文件大于128M会切分成多个block进行存储。
5.2 Block副本放置策略
第一个副本:放置在上传文件的DataNode,也就是Client所在节点上;如果是集群外提交,则随机挑选一台磁盘不太满,CPU不太忙的节点。
第二个副本:放置在于第一个副本不同的机架的节点上。
第三个副本:与第二个副本相同机架的随机节点。
更多副本:随机节点存放。
六、读写流程
6.1 hdfs写数据流程
- 客户端会创建DistributedFileSystem对象,DistributedFileSystem会发起对namenode的一个RPC连接,请求创建一个文件,不包含关于block块的请求。namenode会执行各种各样的检查,确保要创建的文件不存在,并且客户端有创建文件的权限。如果检查通过,namenode会创建一个文件(在edits中,同时更新内存状态),否则创建失败,客户端抛异常IOException。
- NN在文件创建后,返回给HDFS Client可以开始上传文件块。
- DistributedFileSystem返回一个FSDataOutputStream对象给客户端用于写数据。FSDataOutputStream封装了一个DFSOutputStream对象负责客户端跟datanode以及namenode的通信。
- 客户端中的FSDataOutputStream对象将数据切分为小的packet数据包(64kb,core-default.xml:file.client-write-packet-size默认值65536),并写入到一个内部队列(“数据队列”)。DataStreamer会读取其中内容,并请求namenode返回一个datanode列表来存储当前block副本。列表中的datanode会形成管线,DataStreamer将数据包发送给管线中的第一个datanode,第一个datanode将接收到的数据发送给第二个datanode,第二个发送给第三个,依次类推。
- FSDataOutputStream维护着一个数据包的队列,这的数据包是需要写入到datanode中的,该队列称为确认队列。当一个数据包在管线中所有datanode中写入完成,就从ack队列中移除该数据包。如果在数据写入期间datanode发生故障,则执行以下操作
(1)关闭管线,把确认队列中的所有包都添加回数据队列的最前端,以保证故障节点下游的datanode不会漏 掉任何一个数据包。
(2)为存储在另一正常datanode的当前数据块指定一个新的标志,并将该标志传送给namenode,以便故障datanode在恢复后可以删除存储的部分数据块。
(3)从管线中删除故障数据节点并且把余下的数据块写入管线中另外两个正常的datanode。namenode在检测到副本数量不足时,会在另一个节点上创建新的副本。
(4)后续的数据块继续正常接受处理。
(5)在一个块被写入期间可能会有多个datanode同时发生故障,但非常少见。只要设置了dfs.replication.min的副本数(默认为1),写操作就会成功, - 当block传输完成,DN会向NN汇报block信息,同时Client继续传输下一个block,如果有多个block,则会反复从步骤4开始执行。
- 当客户端完成了数据的传输,调用数据流的close方法。该方法将数据队列中的剩余数据包写到datanode的管线并等待管线的确认。
- 客户端收到管线中所有正常datanode的确认消息后,通知namenode文件写入成功。
6.2 hdfs读数据流程
- 客户端通过FileSystem对象的open方法打开希望读取的文件,DistributedFileSystem对象通过RPC调用namenode,以确保文件起始位置。对于每个block,namenode返回存有该副本的datanode地址。这些datanode根据它们与客户端的距离来排序。如果客户端本身就是一个datanode,并保存有相应block一个副本,会从本地读取这个block数据。
- DistributedFileSystem返回一个FSDataInputStream对象给客户端读取数据。该对象管理着datanode和namenode的I/O,用于给客户端使用。客户端对这个输入调用read方法,存储着文件起始几个block的datanode地址的DFSInputStream连接距离最近的datanode。通过对数据流反复调用read方法,可以将数据从datnaode传输到客户端。到达block的末端时,DFSInputSream关闭与该datanode的连接,然后寻找下一个block的最佳datanode。客户端只需要读取连续的流,并且对于客户端都是透明的。
- 客户端从流中读取数据时,block是按照打开DFSInputStream与datanode新建连接的顺序读取的。它也会根据需要询问namenode来检索下一批数据块的datanode的位置。一旦客户端完成读取,就close掉FSDataInputStream的输入流。
- 在读取数据的时候如果DFSInputStream在与datanode通信时遇到错误,会尝试从这个块的一个最近邻datanode读取数据。同时也记住故障datanode,保证以后不会反复读取该节点上后续的block。DFSInputStream也会通过校验和确认从datanode发来的数据是否完整。如果发现有损坏的块,DFSInputStream会尝试从其他datanode读取其副本并通知namenode。
- Client下载完block后会验证DN中的MD5,保证块数据的完整性。