HDFS 是一个适合部署在廉价机器上的,具有高度容错性的,高吞吐量的分布式文件系统。
运行在HDFS上的应用具有很大的数据集。HDFS上的一个典型文件大小一般都在G字节至T字节。因此,HDFS被设计成支持大文件存储,能在一个集群里扩展到数百个节点,能够存储海量的数据。
比如说,某个表 100亿条数据,你还用MySQL 去存么?一台机器你的配置得多高才行,再考虑一下查询的效率。如果用了分布式存储之后,也就是说,这些数据放到N多台机器里面去存储,每个机器只存储这个100亿数据的中的一部分,比如一台机器存 500万数据,HDFS 的定位就是针对这种超大的数据集存储的。
HDFS可能由成百上千的服务器所构成,每个服务器上存储着文件系统的部分数据。而存储这些数据的机器出现故障的几率还是蛮高的,比如磁盘损坏,不能读写,网络故障等原因就会导致这台机器不能正常工作了。
因此错误检测和快速、自动恢复是HDFS最核心的架构目标,一旦HDFS 检测到集群中的某台机器出现问题,然后可以对故障进行恢复。
运行在HDFS 上面的应用和咱们平常写的这些普通应用不同,HDFS 读写文件系统上的数据的时候,是基于一种流的概念来读的,所谓的流式处理,就是批量对文件进行读写,保证高吞吐量的文件读写,不是低延迟的文件读写。
HDFS 是应用在离线批处理场景上面的,尤其是现在的离线数据仓库,用来做数据分析的,我们现在的离线分析,都是今天凌晨去把源系统的数据抽过来放到HDFS 里头,然后处理,都是一批一批的去处理这个数据,不是来一条就计算一条。
同时支持对文件的读和写,需要处理大量的并发冲突问题,想想平常大家在写代码的时候对有读,有写的集合这些加的读写锁,不加会出N多问题。 HDFS 这种大规模数据集的分布式存储,它的模型必须得去简化,它的文件只能一次写入,之后是只能去追加,不能随便修改之前的数据。
所以它的理念就是 :write-once,ready-many-times,一次写,然后多次读,这样就没有数据读写并发冲突,以及数据如何维护一致性的问题了。
这种一次写,多次读的模型,也能大大提升HDFS 的吞吐量。
大家想想,咱们在读取数据的时候,是从本地磁盘读取数据快,还是从网络中的其他机器上读取数据快?是不是数据越靠近这个机器,速度越快,效率越高。
所以说,在分布在多台机器上的数据,进行分布式的计算,需要让你的计算任务更靠近这些数据,而不是在集群里面通过网络到处去传输数据。
分布式系统中比较经典的架构是master-slave ,HDFS 就是采用的这种master-slave 架构,一个HDFS 集群是由一个NameNode 和多个DataNode 组成的。
NameNode 是一个进程,JVM 进程,也是一个系统,我们可以认为 NameNode 就是这个master (可以理解成一个大管家),在一个普通的HDFS 集群里面,NameNode 只有一个。它负责管理文件系统命名空间(filesystem namespace)以及客户端对文件的访问。
DataNode 也是一个进程,集群中的每台机器上都有一个DataNode 进程,它主要负责本机上的数据的存储。
命名空间,怎么理解?就从我们的电脑的文件夹来说,存放的数据都是按照这种目录-> 文件的方式,目录下面可能又是下一层目录。
比如下面的我电脑上的目录结构 ,/文稿/数仓/ 这个目录下面有文件 数仓建设方法xxx.doc 和 目录 /12_资料 ,目录 /12_资料 下面有 04_大数据体系技术结构xxx.doc 这些文档。
所以说,这套文件系统的目录的层级结构和文件的对应关系,就是所谓的 Filesystem Namspace ,这套东西存放在 NameNode 。
在HDFS 集群中,一个大文件会分成N多个块进行拆分,存放在不同机器上。比如说现在有一个 1G的大文件,那么HDFS 会对他进行拆分,比如说 128M 一个,那么就差比多分成8 个文件块,然后放到不同的机器上,也就是DataNode 上,DataNode 就是负责来存储这些文件块的。
一个文件被拆分成了多少个文件块,每个文件块存在哪个DataNode 上,NameNode 都是知道的,所以说 NameNode 就是一个HDFS 集群的大管家。
当你要去读取上面那个大文件的时候,会先跟NameNode 这个大管家去询问一下,这个文件件对应哪几个文件块,每个文件块在哪个位置,然后NameNode告诉你哪些DataNode 上,然后你就可以去对应的DataNode 去拿了。
NameNode 上面存放着HDFS 的,任何对文件系统元数据产生的修改操作,NameNode 都会把这一个一个的操作记录在一个叫EditsLog 的操作日志里面。
例如,在HDFS中创建一个文件,Namenode就会在Editlog中插入一条记录来表示;同样地,修改文件的副本系数也将往Editlog插入一条记录。Namenode在本地操作系统的文件系统中存储这个Editlog 。在hdfs上创建目录,hadoop fs -mkdir -p /usr/dir1,创建一个目录层级结构
然后整个文件系统组织结构,包括数据块到文件的映射、文件的属性等,都存储在一个称为FsImage的文件中,这个文件也是放在Namenode所在的本地文件系统上。
NameNode 在FsImage 文件里面存放元数据之前,会现在内存中保存一段时间,如果每次元数据出现更改就写一次文件,性能是很差的。所以说,NameNode 会每隔一段时间就会去读取磁盘的Editlog 文件出来,全部应用到内存的 fsimage 缓存里面去,然后将fsimage 重新dump 一份到磁盘里面去,然后把前面应用过的Editlog 文件清空掉。
上面把editlog 从磁盘读取出来应用到FsImage 中,然后在把FsImage 保存到磁盘中,最后再删除Editslog 的这个操作,就叫做checkpoint ,这个 checkpoint 的时间间隔我们是可以自己去配置的,在NameNode 启动的时候,它也会先从磁盘上读取edits log 和 Fsimage 在内存中构造一份缓存数据出来。
dfs.namenode.checkpoint.period:这个参数配置几秒钟执行一次checkpoint
dfs.namenode.checkpoint.txns:这个参数配置当edits log里有多少条数据的时候,就执行一次checkpoint
Hadoop 1.x 的时代,NameNode启动的时候,是将 FsImage 读取到内存中,然后再把 EditsLog 的日志内容还原到fsimgae 中,保持内存中的元数据是最新的,然后将最新的fsimage 写入到磁盘里面去,最后清空掉 Editlog 文件。
接下来,客户端在不断的操作HDFS 集群,NameNode 的元数据会不断的进行修改,这些修改都是直接修改内存里面的FsImage 缓存,同时修改元数据的日志文件会被追加到edits log 文件里面去。
有一个问题,对元数据的修改日志会一直追加到edits log 文件里面去,如果他一直被追加,将会变得越来越大,得等到下一次 NameNode 重启,edits log 跟fsimage 合并之后才能被清空。edits log 很大的话,那么在进行合并的时候,将会花费大量的时间,可能导致NameNode 启动变得越来越慢。
所以有一个思路就是去定时合并 edits log 与 fsimage ,合并后,就写入新fsimage 到磁盘上,同时清空edits log ,这样edits log 就不会变得很大了。
所以说hdfs 还有个角色就是 Secondary NameNode ,它会部署到另外一台机器上,它就来专门干合并 edits log 和 fsimage ,每隔时间执行一次,执行的时候,告诉NameNode 别写现在这个 edits log 了,单独开一个新的 edits log 写。然后将 NameNode 的 fsimage 文件和 edits log 拉到本地来,在内存中进行合并,合并完成后,又推送给 NameNode 。
这个操作就是所谓的 checkpoint 操作,这样子一来,edits log 就不会太大 了。
BackupNode 也是在Hadoop1.x 中提供的一种机制,它的思想就是优化和替换checkpoint 那种下载edits log 和fsimage 的合并思路。
backup node 的思路就是在内存存放一份和 namenode 一样的fsimage 数据,同时接受 NameNode 发过来的 edits log 数据流,获取到edits log 数据流就往自己本地的edits log 写一条。
同时还会将这条edits log 回放到自己内存里面的 fsimage 里面去,这样内存中的fsimage 就namenode 内存中的fsimage 一毛一样了。
backup node 也会定期执行一次 checkpoint 操作,把自己本次内存中的 fsimage 写到自己磁盘中,替换掉 老的 fsimage 文件,然后清除edits log 。
一个NameNode 只能挂一个 backup node ,用 backup node的好处在于不用让namenode 自己来维护这么一份edits log 文件,不用自己写磁盘,直接就维护自己内存中的这份fsimage 就行了,然后每次接收到要一条修改 元数据的操作,就应用到自己内存中的fsimage ,同时 把edits log 数据流推送给backup node 就行了。
每次namenode 重启的时候,要用-importCheckpoint 去其他地方加载fsimage 到自己本次里面来,这个 -importCheckpoint 数据就从 backup node 里面拿。
以上就是Hadoop 1.x 对元数据的管理,Hadoop 2.x已经不是这么玩儿的了。
在 Hadoop1.x 时代,NameNode 挂一个 Secondary NameNode 的方式来管理元数据会 元数据丢失和高可用问题。
高可用问题,一旦NameNode 出现故障,宕机的情况,会导致整个集群不可用。 数据丢失问题,如果NamaNode 出现磁盘损坏的情况,那么会导致一定的数据丢失,上一次checkpoint 之后的edits log 都会丢失掉。
在Hadoop 2.x 中,解决了元数据丢失和高可用问题。
Hadoop 2.x 集群会启动两个NameNode ,一个是 active 状态,一个是 standby 状态,我们可以理解为主备,active 状态的是主,standby 状态的是备。客户端所有的操作都是发送到 active 状态的 NameNode ,而 standby 状态的 NameNode 则会作为 active NameNode 的热备,不停的同步 元数据。
standby 状态的 NameNode 并不是直接向 NameNode 去请求元数据的,还有个中间商,Journal Node ,用来存储 edits log 的,一般Journal Node 也是集群,三台起步。
NameNode 有元数据变更的时候,就会将 edits log 写入到 Journal node 集群的大多数节点里面去。比如现在Journal Node 集群是3台,大多数节点就是 3/2 + 1 = 2 ,也就是 NameNode 写入 edits log 到2台 集群,就视为写入成功了。
然后 StandbyNameNode 就会一直监控 JournalNode 里面的edits log 变更,只要有变更,就会拉取新的 edits log 到本地来,然后应用到自己的内存里面去,跟active NameNode 保持一致。
当active NameNode 挂掉之后,standby NameNode 会立马感知到,然后会确保自己从 journal node 读取到的是所有的edits log ,内存中的 fsimage 也是最新的数据,然后将自己切换为 active NameNode。
这样一来,数据丢失的问题解决了,一台机器宕机,另外一台机器立马切换成 active namenode ,继续对外提供服务,高可用问题也解决了。
那么 两台 NameNode 是如何做到在一台故障之后,另外一台自动切换的。
靠 的就是 ZKFailoverController 进程,简称 ZKFC ,每个 NameNode 都会有一个 ZKFC 进程,他们会不停的监控两个 NameNode 的状态,并且在 Zookeeper 上维护两个 NameNode 的状态。
如果说 active NameNode 挂掉了,ZKFC 里面的 HealthMonitor 会监控到,然后ZKFC 里面的 FailoverController 就会通知 NameNode 挂了,然后 FailoverController 会找一 个ActiveStandbyElector 的组件来进行重新主备选举。
ActiveStandbyElector 会基于 zk 来重新选举出来一个standby NameNode 作为 主的 NameNode。
然后 zk 会通知这个standby NameNode 上的 ZKFC 中的 ActiveStandbyElector 组件,通知FailoverController 要切换 standby 状态为 active 状态,然后 FailoverController 再去通知 NameNode 切换为 active NameNode 。
这样,就做到来主备切换,实现了HA。
在 Hadoop 1.x 中,backup node 会定期执行一次 checkpoint 操作,把自己本次内存中的 fsimage 写到自己磁盘中,替换掉 老的 fsimage 文件,然后清除edits log 。
在 Hadoop 2.x 中,Standby NameNode 其实 跟 1.x 中的 backup node 是相似的,也会定期去执行一次 checkpoint 操作。
Standby NameNode 会运行一个叫 CheckpointerThread 的后台线程,默认1小时一次,或者说要是有100万条 edits log 没有合并到 fsimage 里面去,那么就执行一次 checkpoint 操作。
把内存中最新的 fsimage 写到磁盘里面去,同时清空掉 edits log ,最后还会将最新的fsimage 文件推送给 active NameNode ,替换 active NameNode 老的 fsimage ,同时也清空掉 active NameNode 上老的 edits log 文件。
如果你需要上传一个很大的文件,hdfs 客户端在提交上传命令的时候,首先 NameNode 会根据文件的大小,对文件拆分成多个 block文件块 ,2.x 默认是 128 M,1.x 是64 M。
NameNode 会规划好,每一个block 文件块存储到哪个DataNode 里面去,他会根据每个DataNode 的数据存储情况,尽可能的将数据分散,然后hdfs 客户端就会把文件块上传到各个DataNode 上面去DataNode 收到数据后, 会将这些文件块存储到不同的文件目录里面去,他会建立一定的层级结构来存储这些文件块,不会放到一个文件夹下面去。
DataNode 在启动的时候,还会扫描自己本地的文件数据目录,向NameNode 汇报自己保持了哪些文件的文件块列表。
基于block 拆分来存储一个大文件,存储的问题倒是解决了,没啥毛病。但是还有个问题就是说如果某一台机器宕机了,比如说资源消耗过大,宕机,或者重启啥的,那么存放在这个机器上的 block 就没法访问了,本来一个文件是分成10 个block 块的,现在一个故障了,只有9个正常的块了,那么客户端在读取的时候,你这个数据就不全了。
那么如何解决上面集群之间的各种机器故障进行容错呢,答案是 副本机制。
HDFS 里面有一个参数叫做 replication factor,复制因子,也就是说,每一个 block ,都给你复制几份放到不同的机器上去,一个机器宕机不影响,另外的机器上有一摸一样的备份。
HDFS 在写文件的时候,默认每个block 就是3个副本,NameNode 会先挑出来3个DataNode ,每一个 DataNode 放一个 block,返回给客户端,然后客户端就向第一个DataNode 传输 block ,第一个 DataNode 写入这个block 到本地之后,这个DataNode 会向第二个DataNode 复制 block ,第二个DataNode 写入本地之后,又向第三个DataNode 复制 block。
在 HDFS 集群的机器于机器之间进行是要进行通信的,机器和机器可能存在不同的机架上面,同一个机架上面的机器之间进行数据传输肯定是快于不同的机架上面的机器的。
一个 block ,一般默认是 3个副本,那么 NameNode 会把2个副本放到一个机架上的机器,一个副本放到另外一个机架上的机器。
同一个机架上的数据传输是非常快的,还有一点就是就算你这个机架上的所有机器宕机了,那么另外一个机架上的机器还有一份这个block,可以正常对外提供服务。
在 NameNode 刚启动的时候,就会进入一个模式,叫做安全模式,safe mode,在这个模式下,hdfs集群是不会进行 block 的复制的
这个时候 NameNode 会等着从各个 DataNode 获取心跳和 block report,然后看看集群里的整体的block情况,以及每个block有几个副本,默认是要有3个副本的,如果一个block有3个副本,那么就ok了,安全了
如果一定比例(80%)的block都是有足够的3个副本的,那么namenode就会退出安全模式。
NameNode 一直处于 safe mode状态下,就是因为没有达到一定的比例,比如说只有50%的block是有3个副本的。
此时如果发现有某个block副本数量不够(比如只有2个副本)的,就指示 DataNode 复制足够的副本数量
HDFS 里面所有的文件、目录、和配置信息都是存放在NameNode 里面的,当你的HDFS 集群上万台几十万台,数据量超大,你的元数据大小可能都有几百个G,存放在一个NameNode 明显是不得行的。
为了解决这个问题,HDFS 出了一个联邦机制,也就是说,一个集群里面有多个NameNode ,每个NameNode 存储一部分的元数据,不管元数据怎么涨,都可以进行横向扩展NameNode 机器来解决,实际情况是很多公司根本就用不上这个联邦机制,没有这个体量。
在联邦机制下,每个 active NameNode存储一部分 元数据,然后每个 active NameNode 后面还挂一个 standby NameNode ,这样一来,横向扩展跟高可用的问题都解决了。
虽然 NameNode 有多个,但是DataNode 还是一套,block 还是存放在一个 DataNode 集群里面的,只是说 每个DataNode 会向所有的 NameNode 汇报自己的心跳和 block report 情况。
** 愿你尝遍烟火,依然相信,人间值得! **
本文由博客群发一文多发等运营工具平台 OpenWrite 发布