导读:最近因工作需要,学习了下hdfs,将学习笔记分享出来,与大家共勉,也欢迎大家指正。
一、HDFS的特点
1:一次写入,多次读取:HDFS适合一次写入多次读取的模式,不支持对文件系统进行大量的随机修改。简化数据一致性处理。
2:适合批量处理文件,不建议大量存储小文件,其更倾向于高吞吐而不是低延迟。
3:成本低廉,廉价的机器就可以组成一个大集群。
二、HDFS的架构
在hadoop1.x版本中,HDFS主要有3部分组织,NameNode,DataNode,SeconaryNameNode。HDFS的架构类似于master/slave,一个NameNode,多个DataNode,而SeconaryNameNode主要是打杂用的(Hadoop2.x版本不需要SeconaryNameNode了,这个咱们后面再讲)。
NameNode是整个集群的中心,管理着文件系统的元数据信息,元数据包括文件名,数据块等信息,当客户端有一个读请求过来时,首先需要和NameNode通讯获取文件对应数据块对应的Datanode信息,然后才开读取数据块并最终合并。当客户端有写请求过来时,首先NameNode会记录edits操作日志。真正的IO操作时在DataNode上执行。NameNode和DataNode通过心跳机制通讯,每个数据块默认3块副本。
2.1 NameNode
NameNode是hdfs的master节点,管理文件系统的命名空间和元数据,负责client读文件的处理(提供block和dataNode之间的映射关系)
NameNode支持将文件的元数据持久化到磁盘文件,分别为fsimage(命名空间镜像)和edits(操作日志)。NameNode会周期性的和DataNode之间通过心跳机制收集数据块信息(可以抽象理解为Map<String,dataNodes>()),块状态报告包含了一个DataNode上所有的数据块的列表。从而使NameNode持有数据块和dataNode之间的映射关系,然而在hadoop1.x版本NameNode存在单点问题,一旦出现故障,整个HDFS集群将不可用。
fsimage:元数据镜像文件(存储在磁盘,二进制文件)。存储某一时段NameNode内存元数据信息,当NameNode重启的时候,NameNode会合并edits操作到fsimage文件中然后一次性加载到内存,这样内存中就有了元数据了。所以fsimage文件非常重要,我们可以将其写入多个硬盘,如果NameNode主机发生故障,最简单的办法就是使用写入远程文件系统的fsimage副本。
NameNode启动时的操作如图:
由于fsimage是二进制文件,单独打开不好查看,hdfs提供了命令来查看
hadoop oiv -i
/usr/local/hadoop/tmp/dfs/name/current/fsimage_0000000000000002095 -o
/hadooplog/fsimaage.xml -p XML
表示将fsimage_0000000000000002095文件转成xml格式的fsimage.xml。
edits:存储对文件系统的操作信息,比如创建,更新等,edits文件会随着集群的操作会越来越大,每次合并时间会越来越长,这就是一会我们要讲的secondaryNameNode的作用。
那么如何查看edits文件呢?
hdfs oev -i
/usr/local/hadoop/tmp/dfs/name/current/edits_0000000000000001850
-0000000000000002030 -o /hadooplog/edit.xml
metadata:元数据,存储在内存。
2.2 DataNode
DataNode是HDFS的slave节点,存储文件系统真实的数据信息,客户端对hdfs的IO操作实际是和DataNode打交道,当DataNode启动的时候,会像NameNode注册,注册通过后周期性的像NameNode汇报块信息,同时NameNode通过心跳检测来判断DataNode是否存活,如果DataNode故障会将该节点上的数据块转移到其它可用的DataNode节点.
2.3 SeconaryNameNode
前面我们提到,fsiamge和edits文件每次在NameNode重启的时候会合并一次然后加载在内存中,随机集群运行时间的不断加长,加上NameNode不可能总是重启,那么就会有几个问题
1、edits文件越来越大,如何维护
2、如果依靠NameNode重启来合并文件,edits越大,重启时间越长
为了克服这些问题,SeconaryNameNode就出现了,它是NN的一个助手,定期通过http get的方式获取NN上的fsiamge和edits文件进行合并,然后通过http post的方式将最新的文件给NN,最后NN利用最新的fsimage替换旧fsiamge。
其工作图如下:
三、副本
hdfs为了数据的可靠性引进了副本机制,一个数据块默认有3块副本,并且每块副本存放在不同的节点上,副本的存放依赖于机架感知功能
第一个副本默认放在客户端节点所在的节点上(rack1/01.node)
第二个副本放在不同于第一个节点机架的某个节点上(rack2/01.node)
第三个副本放在不同于第二个节点不同集群的某个节点(rack1/02.node)
更多的副本随机存储
3.1 副本选择
当客户端需要读取文件的时候,首先需要向NN获取对应的元数据,NN会告知客户端数据块和DataNode的映射关系,
比如一个文件有256M就是有2个block,返回的时候类似
Map<String,String> map = new ConcurrentHashMap<String,String>();
map.put("block1","node1,node2");
map.put("block2","node1,node2");
hdfs实现肯定不是这样,大家可以这样去想象,然后客户端会依次读取数据块直到将文件所对应的数据块读取完。
其实NameNode在返回给客户端数据对应的DataNode的时候已经进行了排序
1.将离客户端最近的DataNode排在前面
2.客户端机架上的其他副本节点
3.同一个数据中心集群的其他节点副本
4.不同数据中心集群节点副本
比如有如下的集群环境
其中数据中心C1下面的集群RK1下面的D1,D2,D3是同一个机架下,客户端在读取数据的时候,假如客户端就在C1/RK1/D1那么它就会优先读取D1上的数据副本。
四、HDFS的数据健壮性
1.NameNode出错
NamoNode出错一般是元数据损坏,HDFS支持配置多个Fsimage和edits的拷贝,任何对FsImage或者Editlog的修改,都将同步到它们的副本上。
2.DataNode出错
Datanode出错可能引起一些block的副本数目低于指定值,NameNode不断地跟踪需要复制的 block,在任何需要的情况下启动复制。
3、其它网络原因
主要通过NN的心跳机制实现,标记一些失效的节点,复制block到其他节点上。
五、HDFS写文件原理
hadoop RPC的函数之间调用主要是通过JAVA的动态代理和反射机制实现,而网络层则是通过tcp/ip进行通信,而hdfs的文件上传主要也是通过hadoop的rpc机制实现。
当客户端需要上传一个文件的时候,主要执行下面几步操作:
1、首先需要初始化一个DistributeFileSystem,DistributeFileSystem对象通过RPC和NameNode进行通信,首先NameNode需要将客户端的写操作记录在edit.log里面,然后返回给客户端分配给它的DataNode列表(哪些DataNode可用),DistributedFileSystem返回DFSOutputStream给客户端开始写数据。
2、DFSOutputStream准备写数据:首先其会将数据根据配置的数据块大小进行分块存储在dataQueue队列里面(比如一个256M的数据块会被分为2个block),然后Data Streamer开始读取数据块写入DataNode。根据备份数和NameNode返回的DataNode列表构成一个管道线(比如备份数是3,那么这个管道线里面就有3个DataNode),当第一个DataNode写入成功后开始复制操作,即第一个复制到第二个DataNode,第二个DataNode复制block到第三个DataNode,当一个block写入成功后,ackQueue会移除这个block,这样一个数据块就写入成功了,然后依次写入第二个直到该文件被成功上传。当我们在写入的过程中失败了,那么ackQueue也会移除block并把其重新放到dataQueue的队首重新开始写入。在一个管道中,假如有3台DataNode机器,1->2->3,当某台机器宕机后,在还在正常运行的datanode上的当前block上做一个标志,这样当宕掉的datanode重新启动以后namenode就会知道该datanode上哪个block是刚才宕机时残留下的局部损坏block,从而可以把它删掉,根据备份数,nameNode知道目前管道里面的dataNode不足,就会再找一个节点加入到管道里面,后续数据的写入继续。
六、HDFS读文件原理
hdfs文件的读取比文件的写入要简单很多,主要分为以下几步:
1、客户端通过调用FileSystem对象的open()方法来打开文件。
2、DistributedFileSystem通过RPC调用询问NameNode来得到此文件最开始几个block的文件位置。对每一个block来说,namenode返回拥有此block备份的所有datanode的地址信息。如果客户端本身就是一个datanode(如客户端是一个mapreduce任务)并且此datanode本身就有所需文件block的话,客户端便从本地读取文件。
3、以上步骤完成后,DistributedFileSystem会返回一个FSDataInputStream客户端可以从FSDataInputStream中读取数据。FSDataInputStream包装了一个DFSInputSteam类,用来处理namenode和datanode的I/O操作。
4、客户端然后执行read()方法,DFSInputStream连接到第一个datanode(也即最近的datanode)来获取数据。通过重复调用read()方法,文件内的数据就被流式的送到了客户端。当读到该block的末尾时,DFSInputStream就会关闭指向该block的流,转而找到下一个block的位置信息然后重复调用read()方法继续对该block的流式读取。
5、当整个文件读取完毕时,客户端调用FSDataInputSteam中的close()方法关闭文件输入流。
6、如果在读某个block是DFSInputStream检测到错误,DFSInputSteam就会连接下一个datanode以获取此block的其他备份,同时他会记录下以前检测到的坏掉的datanode以免以后再无用的重复读取该datanode。DFSInputSteam也会检查从datanode读取来的数据的校验和,如果发现有数据损坏,它会把坏掉的block报告给namenode同时重新读取其他datanode上的其他block备份。