Apache Hadoop(3)---可靠的分布式存储HDFS

基础概念

GFS在MapReduce框架之间被提出,毕竟首先要解决大数据存储,然后才能开始考虑在此之上的处理问题。虽然说商业级的硬盘故障率较低,硬盘容量也越做越大,但是单盘的容量毕竟有限,而且读写速度也提升的相对没有那么快,摩尔定律也有放缓甚至失效的那一天。在当前网速动辄万兆的对比下,硬盘读写速度才不过百兆或勉强超过千兆。对于一个大容量硬盘。即便是假设它在服务周期内不会出现故障,全盘读取也要耗费数小时以上的时间。

在单盘难以容纳海量数据的情况下采用分布式存储的形式,即将数据分割存储到不同的硬盘上。不管工艺多么好,总会有残次品的存在,况且硬盘的寿命不固定,面对大量的硬盘,偶尔出现一两块坏盘是非常常见的。尤其是硬盘过保了还坚持超期服役,那这个故障率更会大大提升,一块盘坏了,那么上面的数据就全丢了,而修复的价格不菲。

尽管可以通过设置RAID来减少单盘故障带来的影响,但是RAID并没有解决大数据的存储问题,毕竟你不能把所有的硬盘都接到一台机器上,而且万一这台服务器网络故障、断电了,真个存储将处于不可用的状态,在线服务也会大受影响。你看阿里的技术够厉害了吧,还不是敌不过挖掘机的一铲子?

化零为整

把数据分布在不同的硬盘上这个主意不难想到,但是面对频发的故障,如何保证数据的持久性、可用性、负载均衡,乃至于后续的弹性扩容并没有那么容易。

首先对于硬盘故障,我们不能期望通过硬盘修复来找回数据,修复的速度也没有那么迅速。面对硬盘故障,唯一可以做的就是通过冗余,即便丢失了部分数据但是还能通过其他部分补充回来。一种做法是冗余码,把文件分成N块,然后保证在最多丢失M块的情况下还能完整恢复原文件,冗余度越高,容错率越强,当然了额外占用的硬盘容量也会越高,通过冗余码的方式在恢复时也需要额外的计算资源。

HDFS 采取的是另外一种形式,即多副本,将每一个文件复制多份并保存到多个节点上,这样即便是一个节点当机或硬盘故障,还能从其他节点上读取。HDFS封装了很多的底层细节,包括故障之后将丢失的副本冗余到新的节点、数据的负载均衡、弹性扩容等等。对于用户来说,对于数据的存取就像是在单机上操作一样。(当然了,需要使用HDFS的接口,虽然有相关项目把HDFS挂载到本地,但是按照HDFS的设计,速度应该会很慢,关键是HDFS不支持修改)

Block

对于一个大数据文件,它的容量可能已经大大超过了单机所能容纳的规模,这就需要分块存储。在计算机硬盘中是存在块的概念的,但是它的容量很小,按照HDFS的设计,这些块的元信息都是存储在NameNode上,太多的块会降低索引效率,而且不便于管理。考虑到大数据领域文件通常会很大,所以可以将块大小设置的很大。目前默认的块大小是128M。

但是HDFS的块只是逻辑上的,在实际存储的时候还是会按照文件块的实际大小存储,而且有可能会超过块的大小,是一种软限制。

一次写入,多次读取

HDFS主要设计用于解决OLAT即离线数据分析,这种场景下,数据基本上是一次写入多次读取,并且不会修改原有的内容,这简化了HFDS的复杂度。可以在文件末尾添加,但是不能修改原有文件。

组件

在HDFS中,主要有两种组件:namenode和datanode。分布式存储一般采用中心化或去中心化的设计,去中心化的好处在于可以通过一些哈希算法等确定文件所在节点,避免了中心节点的性能和单点故障问题,GlusterFS就采取了这种做法,但是这种做法也会带来一些问题,例如对文件遍历等效率不高。负载均衡问题,各个节点需要监测集群健康状况。

HDFS采用了中心化的形式,元数据存储在namenode节点,datanode只需要负责按照namenode要求存取文件块、监测文件块情况并定时汇报。每次请求都会先与namenode通信。

NameNode

namenode是HDFS中的中心,存储整个文件系统的元数据信息,包括文件名,拥有着,权限,等等信息。此外,还存储了每个文件的块所在的节点,由于块信息变化较快且能够根据datanode的心跳来恢复,所以为了提高效率,块信息是存储在内存中的应用,namenode启动时,会根据datanode的心跳情况来重建。

namenode中存储文件系统状态的数据结构/文件叫image。作为一个负载较高的系统,为了保证一致性等,直接在iamge上进行修改会大大降低系统的吞吐量,所以HDFS采用了写log的方式来记录事实文件修改,然后周期性合并image和editlog,其中image是状态,log是操作。

Secondary NameNode

Secondary NameNode 不是一个必需组件,它存在的意义在于接手namenode合并image和editlog,从而减轻namenode的压力。Secondary NameNode会定期从namenode拉取image和editlog,合并之后再传输回namenode。

需要注意的是,Secondary NameNode并不是namenode的热备份,namenode节点当机之后Secondary NameNode并不会接手,它只负责合并image和editlog。

DataNode

datanode是HDFS中负责数据实际存储的节点,根据namenode的分配,接受客户端数据并写入本地硬盘。在一个分布式集群中,网络故障,节点当机问题时有发生,为了不影响服务可用性,就需要尽快感知到并及时用其他节点替代。在HDFS中,是采用心跳的方式来向namenode汇报,表明当前节点依旧在线。如果namenode长时间没有收到来自datanode的心跳,就会认为这个节点故障或失效,开启启动服务程序,找到该节点还是难过存储的文件块,然后再从其他节点上复制相应的块到其他的节点上 ,保证多副本。(此外,客户端读写故障时也会将故障节点汇报给namenode)

在心跳时,datanode会将该节点中所存储的文件块发给namenode,以便跟踪文件存储情况。

读写步骤

Read

从HDFS读取文件需要经过以下步骤:

  1. 客户端请求namenode,读取某一个文件/目录
  2. namenode检查文件是否存在、用户是否拥有读取权限等验证工作
  3. 验证通过后,namenode返回一个列表,每一项对应一个block以及存储datanode节点(datanode列表按照数据本地性排序,如果block很多,会分批)
  4. 对于每一个block,客户端向第一个datanode建立连接,读取块内容。如果第一个datanode出现故障,客户端会请求下一个datanode
  5. 客户端在接收到block之后会检验文件块是否损坏(通过对比写入时校验和与读取时计算的校验和是否一致,故障则从下一个节点读取,并向namenode汇报)

Hadoop Read

Write

向HDFS中时,需要经过以下几个步骤:

  1. 客户端首先向namenkde发送消息,请求创建文件并分配datanode节点
  2. namenode检查用户权限,文件夹是否已经存在,并分配N个datanode节点(N是副本数,根绝数据本地性等因素排序)
  3. 为了最大限度的利用带宽,HDFS采取了串行写的方案,即客户端向第一个datanode写入文件,第一个datanode向第二个datanode写入,直到最后一个
  4. 由于涉及多个节点(且是串行写),故障率相对较高,对于每一个block,客户端会拆分成一个一个的packet,将发出去的packet添加到等待确认队列中,经过串行写之后,等待最后一个datanode发出确认包之后这个packte才算写入完毕(类似TCP中滑动窗口协议)
  5. 当该blobk写入完毕,客户端向namenode请求一个新的datanode写入列表,然后重复上述步骤
  6. 如果在写入过程中有datanode出现故障,串行写pipeline连接会被关闭,然后重新建立(去掉故障的节点),重新分配block的id(这样故障节点会在恢复后发现存储了无引用的block,从而删除),然后从确认队列中的packet开始继续传输(已经确认的packet不需要重新传输),缺失的副本会由HDFS的检测系统来自动补全
  7. 只要成功写入的datanode节点数不低于用于定义的最小副本数写入都会进行

Hadoop Write

高可用HA

正如前面提到的一样,HDFS是一个存在中心节点的分布式存储系统,按照HDFS的设计,少量datanode的故障不影响系统运行,但是namenode是整个系统的核心,且是单实例的,这就存在单点故障问题。在分布式系统中,解决单点故障,要么是设计成去中心,要么就是通过集群的形式用冗余来保证高可用性。

NameNode 集群

根据namenode的职责,它负责处理image、editlog和文件块信息,由于文件块信息可以通过datanode的心跳包来恢复,所以问题的关键就在于image和editlog的高可用上。首先要保证数据高可用,即节点故障的情况下,依然可以通过启动其他节点来继续服务。在高可用环境下,image和editlog存储在QJM中(它也是一个分布式存储)。

另外一个问题就是,如果做到低延迟切换。在大多数的分布式系统中,对于单点故障的解决都是采用集群的模式,而且采用主从的模式,正常情况下只有一个节点处于活跃状态,其他节点转发请求,当主节点出现故障时,从节点接手继续服务。跟很多系统一样,HDFS namenode集群的选主采用了zookeeper。

参考

Apache Hadoop

Hadoop: The Definitive Guide