HDFS原理入门

    技术2022-07-11  109

    序言

    本篇旨在较为全面的介绍hdfs工作原理。hdfs是Hadoop生态的文件存储系统,相比于其它分布式文件系统,hdfs可以更好的支持大数据计算。 本文会介绍hdfs架构模型,以及主从分布式系统可能遇到的问题及解决方案。本文会以提问->解答的方式阐述,建议读者在阅读的同时思考一下,如果由自己来设计文件系统应该如何实现,遇到了相应问题应该如何解决。本文面向初学者,不要求过多知识储备。 如果有哪里描述不清楚,或者描述错误,欢迎评论区探讨指正。


    HDFS存储模型是什么?

    要想存储大数据,最直观的想法就是将数据切片分开保存,hdfs也确实是这么做的。此外我们还需要知道某条数据存在了哪个分片的节点,这就需要我们保存一个总的“目录”,通过这个目录对数据进行寻址。hdfs称这个目录为:“元数据”,元数据不仅含有位置信息,还有数据的其它属性。这些元数据都保存在“NameNode”节点。任何大数据文件都会按字节切分开,分成若干的“block”,然后保存在“DataNode”节点。NameNode默认是唯一的,客户端通过NameNode的信息来访问DataNode。

    什么是节点? 节点是一个抽象的概念,就是指某个提供服务的点。具体表现为某个启动的Java进程,NameNode和DataNode都可以称为节点。NameNode就是主节点,负责存放目录,告诉客户端去哪里拿数据,DataNode可以有很多,负责自己区域的数据读写。

    block是怎么切割的? block是按照字节切割后分别存在DataNode节点中的,需要注意:

    block大小可以不一样,尾部的block剩下多少是多少。存数据时可以指定block的大小,这需要开发者根据硬件IO来自己调整。block会严格按照字节切分,可能将一条完整数据拆散存,不过不必担心,hdfs在分布式计算时会解决这个问题。block可以指定副本个数,这是hdfs容灾性的体现,后续还会聊到。

    HDFS可以增删改查吗?

    HDFS只能增不能删,因为每个block都是要记录偏移量的(也就是记录block头在原文件的哪个字节),一旦对原文件某个位置删除,其后的所有block偏移量都要修改,不同节点需要互相通信。这种泛洪操作与大数据思想相悖,如果实在需要修改可以通过上层应用将原先位置标记为不可用,再追加新的数据。


    HDFS架构设计是什么样的?

    一主多从的设计,NameNode是主,DataNode是从。NameNode维护元数据,与客户端交互。DataNode负责读写数据,并且维持和NameNode心跳,汇报自己的block信息。

    NameNode中元数据是什么样子的 NameNode会将元数据维护成目录树结构,类似于Linux的虚拟路径。(区别于Windows系统的盘符,并不会像Windows一样将数据指定在c盘或者某个盘下,Linux文件系统是通过挂载机制分区的,可以将不存在的路径随时创建出来) 需要注意的是:元数据是常驻内存,内存往往不稳定,需要通过某些机制来持久化。


    元数据是如何持久化的?

    所谓的持久化就是由于内存随时会挂掉,需要将数据落盘。hdfs使用了较为通用的快照加日志的方案持久化。并且通过SecondaryNameNode辅助恢复数据。

    什么是快照加日志方案?

    快照(FsImage) hdfs会定时将全部元数据序列化后写入磁盘,如果节点挂掉或者关闭,下次开启时只要读入之前落盘的快照,就可以立刻恢复之前的状态。快照的问题是,我们不可能实时保存快照,只能是定期保存一次,例如每小时保存一次,但是对于保存快照到节点挂掉期间做的修改则没有保存,无法恢复。例如8点钟快照落盘,8:30分挂掉,那么8:00到8:30的数据变动会丢失。 快照间隔通过fs.checkpoint.period设置,默认3600秒日志(EditLog) 我们可以将所有的改动都写入日志,然后通过日志逐条恢复修改记录。这会比快照的形式慢很多。但是我们并不需要恢复所有的日志,只需要快照没有记录的那部分日志就可以了。继续上面的例子,我们有了8点的快照,如果机器8:30分挂掉,我们只要恢复8:00之后的日志即可。 日志大小通过fs.checkpoint.size设置,默认64MB

    需要注意: 恢复日志的开销是很大的,实际生产中,我们会让SecondaryNameNode来恢复,并把恢复好的镜像传给NameNode,避免NameNode负荷太大。另外,快照加日志的解决方案用途广泛,比如Redis也是用的这个方案。


    Block副本是如何放置的

    首先说明的是,hdfs节点有主从之分,但是block没有主从之分,所有的block都是副本,统一由NameNode管理,NameNode提供了一套副本放置策略,不同版本策略也略有不同,这里介绍2.x的策略。 介绍放置策略之前,我们了解一下运维的基础知识,服务器有很多种类,有瘦高的一整个的,这是塔式服务器;还有一种是一个高的架子,里面摞满了扁扁的服务器,一个摞一个,每个架子有一个交换机用于同一架子不同服务器交换数据,这个叫机架服务器;还有一种是一个大的框架,里面插满刀片一样的服务器,这个是刀片服务器,性能比前两个都要好。我们这里仅介绍最常见的机架服务器放置策略。 对于每个提交的block,第一个副本会放在客户端提交到的DataNode上,不做任何额外的网络IO,如果是集群外提交,则会随机找一个cpu空闲的节点提交。第二个副本,一定会安排在不同机架的服务器上,这里是与1.x版本不同之处,1.x会放在同机架的不同服务器上,而2.x连机架也是不同的,原因是防止一整个机架挂掉导致数据丢失。第三个副本会放置在与第二个副本同机架的不同服务器上,原因是不想增加不同机架的IO,因为不同机架之间通信会使用更高层级的交换机,代价更高。如果有超过三个的副本,第三个以后的副本都是随机放置。


    HDFS是如何写入数据的?

    客户端节点会先和NameNode交换元数据,告诉NameNode要写文件,以及复制多少份副本(这里假设三个副本),每个block多大等等。这个时候NameNode会触发上文提到的副本放置策略,返回三个DataNode(因为假设有三个副本),这三个DataNode是有排序的,按照放置策略的排序。这时客户端会和排在第一位的DataNode建立TCP连接,再由第一位的DataNode和第二位DataNode建立TCP连接,再由第二位DataNode和第三位DataNode建立TCP连接。注意,客户端只和第一个DataNode连接,剩下都是DataNode自己连,这种连接方式叫做PipeLine。

    TCP连接是如何把block传过去的? 以客户端与第一个DataNode为例,并不是一口气将整个block都传过去,而是拆成64kB的小包,附加连接头以及校验和等信息,通过小包一个一个传过去的。每个DataNode收到小包后会在内存留一份,磁盘写一份,然后立即给下一个节点传过去,无需等到整个block都传完,所以整个过程是非阻塞的。

    如果写入过程中,Pipeline挂掉一个节点怎么办? 以上面的例子为例,如果第二个DataNode挂掉了,第三个DataNode收不到数据,这时会找第一个DataNode要数据,假如之前读到了第7个包,就会从第8个包开始要数据。当整个block都传完之后,每个DataNode要通过心跳连接向NameNode汇报block信息,NameNode只会收到两份信息,因为第二个节点挂掉了。这时NameNode会通知第三个节点,在其后重新生成一个DataNode并同步副本。 需要注意:DataNode向NameNode汇报时,client传输仍然是并行的。


    HDFS的主从结构会遇到哪些问题,如何解决?

    实际上不仅是hdfs,任何主从结构的系统都会遇到如下两个问题:

    容灾性问题,由于主节点只有一个,一旦挂了会导致整个系统不可用。并发性问题,客户端请求都会打到主节点,会导致主节点负载压力过大。

    针对容灾性问题,HDFS发展出HA(高可用)模式,针对并发性问题,HDFS提出Federal(联邦)模式。下面我们详谈hdfs这两种解决方案,并由此管窥类似主从结构系统是如何解决容灾与并发性问题的。


    什么是hdfs的HA模式,如何实现的?

    HA是hdfs的高可用模式,通过引入备用NameNode来备份主NameNode,主节点挂掉之后,由备用节点充当主节点。2.x版本仅支持一主一备两个NameNode,在3.x版本最多支持5个备用节点,但是官方建议不超过3个,主要原因是数据同步问题,以下我们介绍2.x版本的解决方案。 HA模式最关键的问题是主备节点的数据同步,hdfs采用的方案是利用JournalNode同步数据,这是出于对CAP定理的妥协。我们将详细讨论这一问题。

    什么是CAP定理? 我们设想一个分布式系统,我们希望这个系统要满足高可用性,可以处理足够多的并发请求,这个可以很容易的做到,只要再系统里多加一些机器(节点),把请求均衡的分发到这些机器上即可。另外,我们还希望这个系统具有数据一致性,即任何节点的数据变动都能很快的传到整个系统,这个也可以通过架构设计解决,我们可以设计为多层的架构,每个节点之下有若干从节点,数据变动可以逐层的传递下去。另外,我们还希望系统具有高容灾性,即便是主节点挂掉一些,系统也能正常运行,这时我们发现不论如何设计都无法同时满足这三个要求,要满足容灾性,必须一定程度的牺牲前两种特性(可用性,一致性)。这就是CAP定理的内容:对于任何的分布式系统,可用性,一致性,容灾性无法同时满足。

    hdfs为何选用JournalNode同步数据? 关于主备节点同步数据,我们设想如下几个方案:

    主备节点采用同步阻塞的TCP连接: 主节点每次变动数据,都会发送给备用节点,同时阻塞掉所有客户端的服务,直到备用节点确认收到变动数据,再继续提供后续服务。 实际上这种模式是完全不可用的,原因在于备用节点挂掉会导致主节点一直阻塞,无法对外服务,从而导致整个系统瘫痪。引入备用节点本来是为了避免系统整体挂掉,现在备用节点反而成了系统的负担,与初衷相悖。主备节点采用异步非阻塞的连接: 主节点变动数据后立刻向备用节点发送,不论备用节点是否收到,主节点都会继续对外提供服务。 这样做满足了可用性,但是无法保证一致性。一旦主节点挂掉,我们通过备用节点恢复数据,此时的备用节点数据并不可靠,可能导致数据丢失等问题。通过mq(消息队列)等中间件同步数据: 假设通过mq同步数据,主节点阻塞的向消息队列写入数据,确认写入后再提供服务,然后备用节点再从消息队列中取数据。由于mq仅提供存取数据的服务,所以挂掉的可能性很小。 这个方案满足了可用性与一致性,但是mq仍然会有挂掉的可能性,一旦挂掉,系统还是会陷入瘫痪,并没有解决容灾性问题。

    那么hdfs最终采用的JournalNode是如何解决的呢?JournalNode实际上是一系列节点组成的集群,主节点的任何改动都会向集群阻塞的同步修改。JournalNode节点会记录这些修改,当有半数以上的节点记录成功后,便返回成功的消息,主节点不再阻塞,可以继续对外提供服务。这种方案也被称为Paxos算法,常被用于分布式系统来解决CAP定理。 首先,主备节点是分离的,备用节点挂掉不会影响主节点功能,保证了可用性。再考虑JournalNode,假如JournalNode集群挂掉了某台机器,或者挂掉了少数几台机器,实际上也不会影响整个系统的可用性,因为只要多数台机器成功同步数据即可,这就保证了系统的容灾性。由于主备节点并不是实时的同步数据,所以没有保证数据的强一致性,但是主节点是阻塞的写入JournalNode,可以保证数据是可靠的,备用节点最终可以通过JournalNode集群取回数据,所以Paxos算法保证的是最终一致性。

    HA模式下,SecondaryNameNode是如何配置的? HA模式下没有SecondaryNameNode概念(还记得吗,上文中提到的帮NameNode恢复日志的角色),由备用节点充当SecondaryNameNode角色。

    主备节点的切换有没有自动化监控的解决方案? 有的。以上我们介绍了主备节点的数据同步,一旦主节点挂掉,需要手动的将备用节点设为主节点,生产环境中更多的是利用zookeeper来进行自动化监控。 对于每个NameNode节点,zookeeper会启动一个进程,称为ZooKeeperFailoverController(以下简称zkfc),这个进程是在NameNode同主机上启动的,这是为了避免网络的不可靠性。每个zkfc会自动绑定同主机的NameNode节点,并且维持和zookeeper集群的心跳连接。 当整个系统启动时,每个zkfc会在zookeeper集群抢一把锁(实际上zookeeper也有目录树结构,抢锁是在创建子目录),抢到锁的zkfc会将自己的NameNode节点升级为主节点,没有抢到锁的就设置为备用节点。 当主节点挂掉时,zkfc会感知到节点不可用,然后通知zookeeper集群,将自己抢到的锁删除。当zookeeper的锁被删除时,会触发删除事件,通过回调机制通知其它zkfc节点,重新开始抢锁。抢到锁的zkfc并不会直接将其绑定的NameNode升级为主节点,而是会探测先前不可用的主节点是否真的不可用,如果确实是不可用的才会将自己的NameNode升级为主节点并对外提供服务。 为什么zkfc要多一步去主节点验证的过程呢?因为还有一种情况是NameNode主节点正常运行,但是绑定的zkfc挂掉了。这种情况下,该zkfc到zookeeper集群的心跳连接也会断掉,此时zookeeper会将之前抢到的锁所在的临时节点删除,然后像刚才一样,触发锁删除事件,通过回调机制通知其它zkfc,当有某个zkfc新一轮抢到锁后,会尝试探测刚才的主NameNode,此时与刚才不同的是,此时的NameNode是正常运行的(只是zkfc挂掉了),抢到锁的zkfc会将原先的主节点降级成备用节点,然后再将自身的NameNode升级为主节点。

    当zkfc挂掉时,为何要将其正常的NameNode降级呢? 因为主节点的zkfc已经不可用了,即便NameNode是正常的,我们也不希望它缺少监控服务,所以要将监控服务(zkfc)也正常的节点设为主节点。


    什么是hdfs的联邦(Federal)模式?

    联邦模式要解决的问题是NameNode负载压力过大的问题,希望引入更多的NameNode来负载并发请求。需要注意,这与刚才提到的HA模式没有任何关系,HA引入新的NameNode是为了备用,是不需要提供服务的,而Federal模式引入的新的NameNode需要一起提供服务。 hdfs提供的方案也是相对简单粗暴的,Federal模式会直接引入多个NameNode来管理相同的DataNode集群。这里我们假设一个集群有三个NameNode,对于每个存储数据的DataNode,都会维护三个目录,对应于三个NameNode。当NameNode管理DataNode时,只会有自己目录下的权限,而无法操作另外两个目录下的文件。实际上达到的效果是多个系统共用了底层的DataNode节点,而这多个系统彼此是隔离的。 这里会遇到一个问题,如果我们要访问某个文件,需要知道它到底属于哪个系统,即我们需要知道到底应该去访问哪个NameNode,HDFS并不会为我们解决这一问题。实际生产中,如果使用到Federal模式,往往需要文件系统管理员根据业务,在多个NameNode上搭建自己公司的业务层,才能实现访问文件的正确路由。


    结语

    以上是笔者对于HDFS系统的有限认知,文章由“柏拉图学院”首发于,欢迎批评与指正。如果有任何的问题,欢迎在评论区讨论。

    Processed: 0.011, SQL: 9