随着数据量越来越大,在一台机器上已经无法存储所有的数据了,那我们会将这些数据分配到不同的机器来进行存储,但是这就带来一个问题: 不方便管理和维护
所以,我们就希望有一个系统可以将这些分布在不同操作服务器上的数据进行 统一管理 ,这就有了 分布式文件系统
在使用HDFS的时候是非常简单的:虽然HDFS是将文件存储到不同的机器上,但是我去使用的时候是把这些文件 当做 是存储在一台机器的方式去使用(背后却是多台机器在执行):
明确一下:HDFS就是一个 分布式文件系统 ,一个文件系统,我们用它来做什么? 存数据呀 。
下面,我们来了解一下HDFS的一些知识,能够帮我们更好地去“使用”HDFS
从上面我们已经提到了,HDFS作为一个分布式文件系统,那么 它的数据是保存在多个系统上的 。例如,下面的图:一个1GB的文件,会被 切分 成几个小的文件,每个服务器都会存放一部分。
那肯定会有人会问:那会切分多少个小文件呢?默认以 128MB 的大小来切分,每个 128MB 的文件,在HDFS叫做 块 (block)
PS:老版本默认是64MB
一个用户发出了一个 1GB 的文件请求给HDFS客户端,HDFS客户端会根据配置(现在默认是 128MB ),对这个文件进行切分,所以HDFS客户端会切分为8个文件(也叫做 block ),然后每个服务器都会存储这些切分后的文件(block)。现在我们假设 每个服务器都存储两份 。
这些存放 真实数据 的服务器,在HDFS领域叫做 DataNode
现在问题来了,HDFS客户端按照配置切分完以后,怎么知道往哪个服务器(DataNode)放数据呢?这个时候,就需要另一个角色了,管理者( NameNode )。
NameNode实际上就是 管理文件的各种信息 (这种信息专业点我们叫做 MetaData “元数据”),其中包括:文文件路径名,每个Block的ID和存放的位置等等。
所以,无论是读还是写,HDFS客户端都会先去找 NameNode ,通过NameNode得知相应的信息,再去找DataNode
作为一个分布式系统(把大文件切分为多个小文件,存储到不同的机器上),如果没有备份的话,只要有其中的一台机器挂了,那就会导致“数据”是不可用状态的。
写到这里,如果看过我的 Kafka 和 ElasticSearch
的文章可能就懂了。其实思想都是一样的。
Kafka对partition备份,ElasticSearch对分片进行备份,而到HDFS就是对Block进行备份。
尽可能将数据备份到不同的机器上,即便某台机器挂了,那就可以将备份数据拉出来用。
对Kafka和ElasticSearch不了解的同学,可以关注我的 GitHub ,搜索关键字即可查询(我觉得还算写得比较通俗易懂的)
注:这里的备份并不需要HDFS客户端去写,只要DataNode之间互相传递数据就好了。
从上面我们可以看到,NameNode是需要处理hdfs客户端请求的。(因为它是存储元数据的地方,无论读写都需要经过它)。
现在问题就来了,NameNode是怎么存放元数据的呢?
说到这里,又想起了Kafka。Kafka也是将partition写到磁盘里边的,但人家是怎么写的? 顺序IO
NameNode同样也是做了这个事:修改内存中的元数据,然后把修改的信息 append (追加)到一个名为 editlog 的文件上。
由于append是顺序IO,所以效率也不会低。现在我们增删改查都是走内存,只不过增删改的时候往磁盘文件 editlog 里边追加一条。这样我们即便重启了NameNode,还是可以通过 editlog 文件将元数据恢复。
现在也有个问题:如果NameNode一直长期运行的话,那 editlog 文件应该会越来越大(因为所有的修改元数据信息都需要在这追加一条)。重启的时候需要依赖 editlog 文件来恢复数据,如果文件特别大,那启动的时候不就特别慢了吗?
的确是如此的,那HDFS是怎么做的呢?为了防止 editlog 过大,导致在重启的时候需要较长的时间恢复数据,所以NameNode会有一个 内存快照 ,叫做 fsimage
说到快照,有没有想起Redis的RDB!!
这样一来,重启的时候只需要加载内存快照 fsimage +部分的 editlog 就可以了。
想法很美好,现实还需要解决一些事:我什么时候生成一个内存快照 fsimage ?我怎么知道加载哪一部分的 editlog ?
问题看起来好像复杂,其实我们就只需要一个 定时任务 。
如果让我自己做的话,我可能会想:我们加一份配置,设置个时间就OK了
HDFS也是类似上面这样干的,只不过它不是在NameNode起个定时的任务跑,而是用了一个新的角色: SecondNameNode 。至于为什么?可能HDFS觉得 合并所耗费的资源太大 了,不同的工作交由不同的服务器来完成,也符合分布式的理念。
现在问题还是来了,此时的架构 NameNode是单机 的。SecondNameNode的作用只是给NameNode合并 editlog 和 fsimage 文件,如果NameNode挂了,那client就请求不到了,而所有的请求都需要走NameNode,这导致整个HDFS集群都不可用了。
于是我们需要保证NameNode是高可用的。一般现在我们会通过 Zookeeper 来实现。架构图如下:
主NameNode和从NameNode需要保持元数据的信息一致(因为如果主NameNode挂了,那从NameNode需要顶上,这时从NameNode需要有主NameNode的信息)。
所以,引入了Shared Edits来实现主从NameNode之间的同步,Shared Edits也叫做 JournalNode 。实际上就是主NameNode如果有更新元数据的信息,它的 editlog 会写到JournalNode,然后从NameNode会在JournalNode读取到变化信息,然后同步。从NameNode也实现了上面所说的SecondNameNode功能(合并editlog和fsimage)
稍微总结一下:
从上面我们就知道,我们的数据是存放在DataNode上的(还会备份)。
如果某个DataNode掉线了,那HDFS是怎么知道的呢?
DataNode启动的时候会去NameNode上注册,他俩会维持 心跳 ,如果超过时间阈值没有收到DataNode的心跳,那HDFS就认为这个DataNode挂了。
还有一个问题就是:我们将Block存到DataNode上,那还是有可能这个DataNode的磁盘 损坏了部分 ,而我们DataNode没有下线,但我们也不知道损坏了。
一个Block除了存放数据的本身,还会存放一份元数据(包括数据块的长度,块数据的校验和,以及时间戳)。DataNode还是会 定期 向NameNode上报所有当前所有Block的信息,通过 元数据就可校验当前的Block是不是正常状态 。
其实在学习HDFS的时候,你会发现很多的思想跟之前学过的都类似。就比如提到的Kafka、Elasticsearch这些常用的分布式组件。
如果对Kafka、Elasticsearch、Zookeeper、Redis等不了解的同学,可以在我的GitHub或公众号里边找对应的文章哦~我觉得还算写得通俗易懂的。
改天整合一下这些框架的持久化特点,再写一篇(因为可以发现,他们的持久化机制都十分类似)