阅读 952

HDFS的基础总结及架构演进

前言

截取知识星球的分享出来,也是对之前的 HDFS 进行一个补充,顺带让大家复习一下

前面两篇 HDFS 在这里:

带你入坑大数据(一) --- HDFS基础概念篇

带你入坑大数据(二) --- HDFS的读写流程和一些重要策略

Coutent

分散存储,冗余存储

这两点我可以展开说明一下,首先我们要清楚,HDFS里面的数据,分为真实数据元数据两种,当然这里面元数据是在 Namenode 里面的,而真实数据是存储在 Datanode 里面的。

比如我们现在要存储一个大文件,分散存储的意思就是,会将这个文件拆分成一个个的数据块block,分别独立存放在某个 Datanode 中。那此时问题就来了,你怎么知道哪个文件是由哪些数据块组成呢?而且这些数据块又分别存在哪些 Datanode 上呢?这就是元数据所起到的作用。

元数据存储其实是在内存和磁盘都存储了一份的,存储在内存的考量主要就是提高响应速度,而存磁盘是为了保证元数据的安全。而这样导致的问题就是:如何保证内存和磁盘的数据一致性问题?这块可以去学习一下 Redis 的做法,既保证效率又保证安全。

而且为什么说HDFS不适合存储小文件,甚至说我们会有很多小文件的合并机制,那是因为元数据并不是一个文件一条元数据,而是每一个数据块都会对应有一个相关的元数据描述,而每个数据块的元数据大小将近150字节,这好比说我们往HDFS存一个100M的视频,对应一条150byte的元数据,如果存100张1M的图片,就会有对应100条150byte的元数据,所以这个资源的浪费是十分可怕的。

而冗余存储就是我们的block会有副本机制,这个副本的存储套路是机架存储策略

机架存储策略

实际机房中,会有机架,每个机架上会有若干台服务器。一般来说我们会把一个block的3个副本分别按照下述方法进行存储:

  1. 第一个副本就存储在一个机架A上 第二个副本存储在和这个block块不同机架(比如机架B)的一个服务器上

  2. 存储第2个副本时会优先把副本存储在不同的机架上,这是为了防止出现一个机架断电的情况,如果副本也存储在同机架上的不同服务器上,这时候数据就可能丢失了。

  3. 第三个副本存储在机架B的另外一个服务器上(注意副本2,3都存储在了机架B)

为什么会这么选择,因为如果我们把副本3也放在另外一个机架C上,副本2和副本3之间的通信就需要副本2通过它的交换机去联系总交换机,然后总交换机去联系机架C的交换机,需要走的路线非常长,而且机房中的带宽资源非常宝贵,如果处于高并发的情况,很容易就把机房的带宽打满,此时整一个集群的响应速度会急剧下降,这时候服务就会出现问题了。

当然我们的副本数也是可以手动通过命令增加的,在客户端访问量多的时候,可以适当分配一下压力

$ hadoop fs -setrep -R 4 path + FileName
复制代码

setrep的意思其实就是set replication,设置副本数的缩写,上面命令就是将副本数设置成4份了,后面跟着文件路径和文件名即可

客户端的交互全部都是和 Namenode 打交道的,这点和 Kafka 一样,永远都是和 leader 打交道,而不是和 follower。但是你要知道,正常的实现数据的上传下载的功能确实是走的 Datanode。

HDFS 的架构

HDFS的架构:主从架构,三大角色

  1. Namenode作为集群的老大,掌管HDFS文件系统的元数据,处理客户端读写请求,HDFS的集群内部数据安全及负载均衡等
  2. Datanode存储整个集群的所有数据块,处理真正的数据读写
  3. SecondaryNamenode严格意义上来说并不属于namenode的备份节点,它主要起到的作用其实是替namenode分担压力,降低负载(元数据的编辑日志合并,也就是edits log)之用

心跳机制

心跳机制解决了HDFS集群间的通信问题,还是NameNode命令DataNode执行操作的途径

  1. master namenode启动之后,会开一个ipc server
  2. DataNode启动,连接NameNode,每隔3s向NameNode发送一个心跳,并携带状态信息
  3. NameNode通过对这个心跳的返回值来给DataNode传达任务指令

心跳机制的作用:

1.NameNode 全权管理数据块的复制,它周期性从集群中的每个 DataNode 接收心跳信号和 block 状态报告,接收到心跳信号意味着该 DataNode 节点工作正常,块状态报告包含了该 DataNode 上所有数据块的列表

2.DataNode启动时向 NameNode 注册,通过后周期性地向 NameNode 上报 block 报告,每3秒向 NameNode 发送一次心跳,NameNode 返回对该 DataNode 的指令,如将数据块复制到另一台机器,或删除某个数据块等···而当某一个 DataNode 超过10min还没向 NameNode 发送心跳,此时 NameNode 就会判定该 DataNode 不可用,此时客户端的读写操作就不会再传达到该 DataNode 上

安全模式

3.hadoop 集群刚开始启动时会进入安全模式(99.99%),就用到了心跳机制,其实就是在集群刚启动的时候,每一个 DataNode 都会向 NameNode 发送 block 报告,NameNode 会统计它们上报的总block数,除以一开始知道的总个数total,当 block/total < 99.99% 时,会触发安全模式,安全模式下客户端就没法向HDFS写数据,只能进行读数据。

而且补充一点,Namenode感知Datanode掉线死亡时间的计算公式为:

timeout = 2 * heartbeat.recheck.interval + 10 * dfs.heartbeat.interval
复制代码

HDFS默认超时时间为630秒,因为默认的 heartbeat.recheck.interval 为5分钟,而 dfs.heartbeat.interval 默认为3秒,而这两个参数理解起来也很简单,一个是重新检查的时间间隔,而另一个是每n秒发送一次心跳的参数,等待10次,不行拉倒。

安全模式的补充

安全模式不仅仅是集群刚启动时等所有的Datanode汇报这一种情况会进入安全模式的,还有就是HDFS数据块丢失达到一个比例的时候,也会自动进入,当然我们也可以手动去进入安全模式。这个比例默认是0.1%,1000个块丢1个已经很严重的事件了。

可以通过 start-balancer.sh 来让HDFS做负载均衡,可是要注意,这个命令是存在一定的问题的,这和Java的垃圾回收机制中的 System.gc() 是一样的。我告诉你,现在要去进行垃圾回收了,可是 JVM 压根就不理咱们,为啥呢?它只会在自己有空,合适的时间去做垃圾回收,而 start-balancer.sh 就也是一样的套路,利用剩余的带宽去做这个事情。

而这个操作也有一定的标准,根据一个数值n规定。每一个节点都计算出一个磁盘的占用量,占用量最大-占用量最小 = 这个标准数值n即可。默认是10%,这样就很好理解了吧。而且这个负载均衡的操作是一定不能影响到客户端的读写业务的,所以HDFS默认会不允许balance操作占用太多的带宽。但是我们可以进行手动调整

hdfs dfs admin -setBalancerBandwidth newbandwidth
复制代码

newbandwidth 的默认单位是字节,所以这个东西自己根据需求调整即可。默认是1M每秒的。

HDFS的缺陷及演进

Hadoop1 版本刚出来的时候是为了解决两个问题:一个是海量数据如何存储的问题,一个是海量数据如何计算的问题

Namenode的工作:

  1. 管理集群元数据信息(文件目录树): 可以简单理解为,一个文件被分为了几个block块,而这些block块又分别存储在了哪些位置。
  2. Namenode为了快速响应用户的操作请求,所以将元数据加载到了内存里面

Datanode的工作:

  1. 存储数据,把上传的数据划分为固定大小的文件块,hadoop1默认64,之后是128M
  2. 为了保证数据安全,每个文件块默认都有三个副本,这里的三个副本其实是总共3个的意思,而不是一份原始3个备份总数为4

HDFS1的架构缺陷

  1. 单点故障问题:Namenode挂掉集群就废了
  2. 内存受限问题:就是Namenode的元数据信息撑爆了内存的情况下,整个集群就瘫痪了

QJM方案解决单点故障问题

其实本身还存在一个多个Namenode共用一个共享目录的解决方式,但是这样也会存在共享目录出现问题的情况,所以就无法满足我们的要求

QJM方案如下:我们单独建立一个JournalNode集群,然后由它们来解决单点故障的问题,JournalNode之间数据是一致的,我们的主Namenode,也就是active Namenode对元数据进行修改的时候,会对JournalNode进行写入操作,然后再由Standby Namenode去进行同步以达到主从Namenode的数据一致。

但是现在还是存在一个问题,就是我们需要手动将standby切换成active,所以此时我们就引入了Zookeeper来解决这个问题,其实存在一些企业会有直接不使用JournalNode而直接使用Zookeeper来代替的方案,因为JournalNode的任务就是保证这些节点的数据一致而已,这个特点通过Zookeeper的原子广播协议是完全可以做到的。

此时在Zookeeper中创建一个锁的目录,然后NameNode启动的时候都会过去抢占锁,两个NameNode谁先抢到,谁就是active状态。而且每一个NameNode上还有一个ZKFC的服务,持续监听NameNode的健康状态,如果active NameNode出现问题,ZKFC将会报告给Zookeeper,然后Zookeeper会将锁分配给standby的NameNode上。使其自动切换为active状态。此时就是HDFS2的架构了。

JournalNode推荐

因为其实JournalNode的任务并不重,所以不需要太过于庞大的集群

200个节点以下 ---> 3个

200~2000个节点 ---> 5个

联邦解决内存受限问题

如上图,就是等于一个横向扩展的方式,既然一个Namenode会撑爆,那么多个Namenode,负责存储不同的元数据信息即可。这就好比我们的C,D,E,F盘,C盘是存放系统的一些东西,D盘拿来装软件,E盘拿来放电影是一个道理。而且联邦机制,HDFS 自动路由。用户不用关心是具体是哪个 namenode 去进行存储了

此时作一个小总结,active+standby 的Namenode形成一组,目的是做高可用,防止单点故障。而多组active+standby形成联邦,目的是解决单组内存不够的问题。

HDFS如何管理元数据

还记得我们搭建集群的时候,在启动集群之前需要做一步“格式化Namenode”的操作吗?这一步的目的就是在磁盘上生成fsimage,也就是我们的元数据目录信息。此时再把这个元数据加载到内存。

如下图,此时假如客户端要上传文件,它就会和内存中的fsimage进行交互,增加一条元数据信息,这个操作就会写入到一份 edit log 中去,顾名思义就是编辑日志。而磁盘中的fsimage此时还是最一开始的那份,不像内存中的fsimage是随时在变化的。此时

内存fsimage = 磁盘fsimage + edit log 
复制代码

此时如果我停止了服务,内存中的fsimage被销毁,此时我只需要将edit log中的记录回放,刷写成新的一份fsimage,此时集群再次启动,再加载到内存中,就恢复成为我们停止服务前的状态了。

此时引入SecondaryNamenode,它的作用就是提高Namenode的恢复速度,大致对操作步骤进行一个阐述:

  1. SecondaryNameNode 会通过http get方式把edits log和fsimage的信息拉取过来
  2. 在SecondaryNameNode中把edits log和fsimage做一个合并,产生一个新的文件叫做 fsimage.ckpt
  3. 在SecondaryNameNode中合并完成之后,再回传给NameNode里面
  4. 这时大概率会有客户端还在对NameNode进行读写操作,也会产生新的日志,此时按照先前未引入SNN的套路继续即可。 在HA方案中这个行为可以交给standby去完成。

双缓冲机制

Namenode里面的元数据是以两种状态进行存储的:

第一状态即是存储在内存里面,也就是刚刚所提到的目录树,它就是一个list,在内存里面更新元数据速度是很快的。但是如果仅仅只在内存里存放元数据,数据是不太安全的。

所以我们在磁盘上也会存储一份元数据,可是此时问题就出现了,我们需要把数据写进磁盘,这个性能肯定是不太好的呀。可NameNode作为整个集群的老大,在hadoop上进行hive,HBASE,spark,flink等计算,这些数据都会不停给NameNode施压写元数据,一天下来一亿条元数据都是可能的,所以NameNode的设计肯定是要支持超高并发的,可是写磁盘这操作是非常非常慢的,一秒几十或者最多几百都已经封顶了,那现在咋办?

首先我们的客户端(这里指的是hive,hbase,spark···都没关系)所产生的数据将会走两个流程,第一个流程会向内存处写入数据,这个过程非常快,也不难理解

这时候肯定就不能直接写内存了,毕竟我们是明知道这东西非常慢的,真的要等它一条条数据写到磁盘,那特么我们都可以双手离开鼠标键盘下班走人了。那NameNode一个东西不行就整个集群都不行了,那现在我们该如何解决?

双缓冲机制就是指我们将会开辟两份一模一样的内存空间,一个为bufCurrent,产生的数据会直接写入到这个bufCurrent,而另一个叫bufReady,在bufCurrent数据写入(其实这里可能不止一条数据,等下会说明)后,两片内存就会exchange(交换)。然后之前的bufCurrent就负责往磁盘上写入数据,之前的bufReady就继续接收客户端写入的数据。其实就是将向磁盘写数据的任务交给了后台去做。这个做法,在JUC里面也有用到

而且在此基础上,hadoop会给每一个元数据信息的修改赋予一个事务ID号,保证操作都是有序的。这也是出于数据的安全考虑。这样整个系统要求的内存会非常大,所以这关乎一个hadoop的优化问题,在之后将会提及。

Finally

最近有在经营自己的知识星球,旨在帮助想要了解大数据的朋友们入门大数据的内容,且和已经从事大数据的开发人员一起进阶,免费但不代表你会没有收获,如果感兴趣的话可以加一下,基本都会更新的比较勤