第3章  Hadoop 分布式文件系统

第3章  Hadoop分布式文件系统

大数据时代必须解决海量数据的高效存储问题,为此,谷歌开发了分布式文件系统(Google File System,GFS),通过网络实现文件在多台机器上的分布式存储,较好地满足了大规模数据存储的需求。Hadoop分布式文件系统(Hadoop Distributed File System,HDFS)是针对GFS的开源实现,它是Hadoop两大核心组成部分之一,提供了在廉价服务器集群中进行大规模分布式文件存储的能力。HDFS具有很好的容错能力,并且兼容廉价的硬件设备,因此,可以以较低的成本利用现有机器实现大流量和大数据量的读写。

本章首先介绍分布式文件系统的基本概念、结构和设计需求,然后介绍HDFS,详细阐述它的重要概念、体系结构、存储原理和读写过程,最后,介绍了一些HDFS编程实践方面的知识。

3.1 分布式文件系统

相对于传统的本地文件系统而言,分布式文件系统(Distributed File System)是一种通过网络实现文件在多台主机上进行分布式存储的文件系统。分布式文件系统的设计一般采用“客户机/服务器”(Client/Server)模式,客户端以特定的通信协议通过网络与服务器建立连接,提出文件访问请求,客户端和服务器可以通过设置访问权来限制请求方对底层数据存储块的访问。

目前,已得到广泛应用的分布式文件系统主要包括GFS和HDFS等,后者是针对前者的开源实现。

3.1.1 计算机集群结构

普通的文件系统只需要单个计算机节点就可以完成文件的存储和处理,单个计算机节点由处理器、内存、高速缓存和本地磁盘构成。

分布式文件系统把文件分布存储到多个计算机节点上,成千上万的计算机节点构成计算机集群。与之前使用多个处理器和专用高级硬件的并行化处理装置不同的是,目前的分布式文件系统所采用的计算机集群都是由普通硬件构成的,这就大大降低了硬件上的开销。

计算机集群的基本架构如图3-1所示。集群中的计算机节点存放在机架(Rack)上,每个机架可以存放8~64个节点,同一机架上的不同节点之间通过网络互连(常采用吉比特以太网),多个不同机架之间采用另一级网络或交换机互连。

3-1

图3-1 计算机集群的基本架构

3.1.2 分布式文件系统的结构

在我们所熟悉的Windows、Linux等操作系统中,文件系统一般会把磁盘空间划分为每512字节一组,称为“磁盘块”,它是文件系统读写操作的最小单位,文件系统的块(Block)通常是磁盘块的整数倍,即每次读写的数据量必须是磁盘块大小的整数倍。

与普通文件系统类似,分布式文件系统也采用了块的概念,文件被分成若干个块进行存储,块是数据读写的基本单元,只不过分布式文件系统的块要比操作系统中的块要大很多,比如,HDFS默认的一个块的大小是64MB。与普通文件不同的是,在分布式文件系统中,如果一个文件小于一个数据块的大小,它并不占用整个数据块的存储空间。

分布式文件系统在物理结构上是由计算机集群中的多个节点构成的,如图3-2所示。这些节点分为两类,一类叫“主节点”(Master Node)或者也被称为“名称节点”(NameNode),另一类叫“从节点”(Slave Node)或者也被称为“数据节点”(DataNode)。名称节点负责文件和目录的创建、删除和重命名等,同时管理着数据节点和文件块的映射关系,因此,客户端只有访问名称节点才能找到请求的文件块所在的位置,进而到相应位置读取所需文件块。数据节点负责数据的存储和读取;在存储时,由名称节点分配存储位置,然后由客户端把数据直接写入相应数据节点;在读取时,客户端从名称节点获得数据节点和文件块的映射关系,然后就可以到相应位置访问文件块。数据节点也要根据名称节点的命令创建、删除数据块和冗余复制。

3-2

图3-2 大规模文件系统的整体结构

计算机集群中的节点可能发生故障,因此,为了保证数据的完整性,分布式文件系统通常采用多副本存储。文件块会被复制为多个副本,存储在不同的节点上,而且,存储同一文件块的不同副本的各个节点,会分布在不同的机架上,这样,在单个节点出现故障时,就可以快速调用副本重启单个节点上的计算过程,而不用重启整个计算过程,整个机架出现故障时也不会丢失所有文件块。文件块的大小和副本个数通常可以由用户指定。

分布式文件系统是针对大规模数据存储而设计的,主要用于处理大规模文件,如TB级文件,处理过小的文件不仅无法充分发挥其优势,而且会严重影响到系统的扩展和性能。

3.1.3 分布式文件系统的设计需求

分布式文件系统的设计目标主要包括透明性、并发控制、可伸缩性、容错以及安全需求等。但是,在具体实现中,不同产品实现的级别和方式都有所不同。表3-1给出了分布式文件系统设计需求及其具体含义,以及HDFS对这些指标的实现情况。

表3-1 分布式文件系统的设计需求

设计需求

含义

HDFS的实现情况

透明性

具备访问透明性、位置透明性、性能和伸缩透明性。访问透明性是指用户不需要专门区分哪些是本地文件,哪些是远程文件,用户能够通过相同的操作来访问本地文件和远程文件资源。位置透明性是指在不改变路径名的前提下,不管文件副本数量和实际存储位置发生何种变化,对用户而言都是透明的,用户不会感受到这种变化,只需要使用相同的路径名就始终可以访问同一个文件。性能和伸缩透明性是指系统中节点的增加或减少以及性能的变化对用户而言是透明的,用户感受不到什么时候一个节点加入或退出了

只能提供一定程度的访问透明性,完全支持位置透明性、性能和伸缩透明性

并发控制

客户端对于文件的读写不应该影响其他客户端对同一个文件的读写

机制非常简单,任何时间都只允许有一个程序写入某个文件

文件复制

一个文件可以拥有在不同位置的多个副本

HDFS采用了多副本机制

硬件和操作系统的异构性

可以在不同的操作系统和计算机上实现同样的客户端和服务器端程序

采用Java语言开发,具有很好的跨平台能力

可伸缩性

支持节点的动态加入或退出

建立在大规模廉价机器上的分布式文件系统集群,具有很好的可伸缩性

容错

保证文件服务在客户端或者服务端出现问题的时候能正常使用

具有多副本机制和故障自动检测、恢复机制

安全

保障系统的安全性

安全性较弱

3.2 HDFS简介

HDFS开源实现了GFS的基本思想。HDFS原来是Apache Nutch搜索引擎的一部分,后来独立出来作为一个Apache子项目,并和MapReduce一起成为Hadoop的核心组成部分。HDFS支持流数据读取和处理超大规模文件,并能够运行在由廉价的普通机器组成的集群上,这主要得益于HDFS在设计之初就充分考虑了实际应用环境的特点,那就是,硬件出错在普通服务器集群中是一种常态,而不是异常,因此,HDFS在设计上采取了多种机制保证在硬件出错的环境中实现数据的完整性。总体而言,HDFS要实现以下目标。

  • 兼容廉价的硬件设备。在成百上千台廉价服务器中存储数据,常会出现节点失效的情况,因此,HDFS设计了快速检测硬件故障和进行自动恢复的机制,可以实现持续监视、错误检查、容错处理和自动恢复,从而使得在硬件出错的情况下也能实现数据的完整性。

  • 流数据读写。普通文件系统主要用于随机读写以及与用户进行交互,而HDFS则是为了满足批量数据处理的要求而设计的,因此,为了提高数据吞吐率,HDFS放松了一些POSIX的要求,从而能够以流式方式来访问文件系统数据。

  • 大数据集。HDFS中的文件通常可以达到GB甚至TB级别,一个数百台机器组成的集群里面可以支持千万级别这样的文件。

  • 简单的文件模型。HDFS采用了“一次写入、多次读取”的简单文件模型,文件一旦完成写入,关闭后就无法再次写入,只能被读取。

  • 强大的跨平台兼容性。HDFS是采用Java语言实现的,具有很好的跨平台兼容性,支持JVM(Java Virtual Machine)的机器都可以运行HDFS。

    HDFS特殊的设计,在实现上述优良特性的同时,也使得自身具有一些应用局限性,主要包括以下几个方面。

  • 不适合低延迟数据访问。HDFS主要是面向大规模数据批量处理而设计的,采用流式数据读取,具有很高的数据吞吐率,但是,这也意味着较高的延迟。因此,HDFS不适合用在需要较低延迟(如数十毫秒)的应用场合。对于低延时要求的应用程序而言,HBase是一个更好的选择。

  • 无法高效存储大量小文件。小文件是指文件大小小于一个块的文件,HDFS无法高效存储和处理大量小文件,过多小文件会给系统扩展性和性能带来诸多问题。首先,HDFS采用名称节点(NameNode)来管理文件系统的元数据,这些元数据被保存在内存中,从而使客户端可以快速获取文件实际存储位置。通常,每个文件、目录和块大约占150字节,如果有1000万个文件,每个文件对应一个块,那么,名称节点至少要消耗3GB的内存来保存这些元数据信息。很显然,这时的元数据检索的效率就比较低了,需要花费较多的时间找到一个文件的实际存储位置。而且,如果继续扩展到数十亿个文件时,名称节点保存元数据所需要的内存空间就会大大增加,以现有的硬件水平,是无法在内存中保存如此大量的元数据的。其次,用MapReduce处理大量小文件时,会产生过多的Map任务,线程管理开销会大大增加,因此,处理大量小文件的速度远远低于处理同等大小的大文件的速度。再次,访问大量小文件的速度远远低于访问几个大文件的速度,因为,访问大量小文件,需要不断从一个数据节点跳到另一个数据节点,严重影响性能。

  • 不支持多用户写入及任意修改文件。HDFS只允许一个文件有一个写入者,不允许多个用户对同一个文件执行写操作,而且只允许对文件执行追加操作,不能执行随机写操作。

3.3 HDFS的相关概念

3.3.1 块

在传统的文件系统中,为了提高磁盘读写效率,一般以数据块为单位,而不是以字节为单位。比如,机械式硬盘(磁盘的一种)包含了磁头和转动部件,在读取数据时有一个寻道的过程,通过转动盘片和移动磁头的位置,来找到数据在机械式硬盘中的存储位置,然后,才能进行读写。在I/O开销中,机械式硬盘的寻址时间是最耗时的部分,一旦找到第一条记录,剩下的顺序读取效率是非常高的。因此,以块为单位读写数据,可以把磁盘寻道时间分摊到大量数据中。

HDFS也同样采用了块的概念,默认的一个块大小是64MB。在HDFS中的文件会被拆分成多个块,每个块作为独立的单元进行存储。我们所熟悉的普通文件系统的块,一般只有几千字节(byte),可以看出,HDFS在块的大小的设计上明显要大于普通文件系统。HDFS这么做的原因,是为了最小化寻址开销。HDFS寻址开销不仅包括磁盘寻道开销,还包括数据块的定位开销。当客户端需要访问一个文件时,首先从名称节点获得组成这个文件的数据块的位置列表,然后,根据位置列表获取实际存储各个数据块的数据节点的位置,最后,数据节点根据数据块信息在本地Linux文件系统中找到对应的文件,并把数据返回给客户端。设计一个比较大的块,可以把上述寻址开销分摊到较多的数据中,降低了单位数据的寻址开销。因此,HDFS在文件块大小设置上要远远大于普通文件系统,以期在处理大规模文件时能够获得更好的性能。当然,块的大小也不宜设置过大,因为,通常MapReduce中的Map任务一次只处理一个块中的数据,如果启动的任务太少,就会降低作业并行处理速度。

HDFS采用抽象的块概念可以带来以下几个明显的好处。

  • 支持大规模文件存储。文件以块为单位进行存储,一个大规模文件可以被分拆成若干个文件块,不同的文件块可以被分发到不同的节点上,因此,一个文件的大小不会受到单个节点的存储容量的限制,可以远远大于网络中任意节点的存储容量。

  • 简化系统设计。首先,大大简化了存储管理,因为文件块大小是固定的,这样就可以很容易计算出一个节点可以存储多少文件块;其次,方便了元数据的管理,元数据不需要和文件块一起存储,可以由其他系统负责管理元数据。

  • 适合数据备份。每个文件块都可以冗余存储到多个节点上,大大提高了系统的容错性和可用性。

3.3.2 名称节点和数据节点

在HDFS中,名称节点(NameNode)负责管理分布式文件系统的命名空间(Namespace),保存了两个核心的数据结构(见图3-3),即FsImage和EditLog,FsImage用于维护文件系统树以及文件树中所有的文件和文件夹的元数据,操作日志文件EditLog中记录了所有针对文件的创建、删除、重命名等操作。名称节点记录了每个文件中各个块所在的数据节点的位置信息,但是,并不持久化存储这些信息,而是在系统每次启动时扫描所有数据节点重构得到这些信息。

图像说明文字

图3-3 名称节点的数据结构

数据节点(DataNode)是分布式文件系统HDFS的工作节点,负责数据的存储和读取,会根据客户端或者是名称节点的调度来进行数据的存储和检索,并且向名称节点定期发送自己所存储的块的列表。每个数据节点中的数据会被保存在各自节点的本地Linux文件系统中。

3.4 HDFS体系结构

3.4.1 概述

HDFS采用了主从(Master/Slave)结构模型,一个HDFS集群包括一个名称节点和若干个数据节点(见图3-4)。名称节点作为中心服务器,负责管理文件系统的命名空间及客户端对文件的访问。集群中的数据节点一般是一个节点运行一个数据节点进程,负责处理文件系统客户端的读/写请求,在名称节点的统一调度下进行数据块的创建、删除和复制等操作。每个数据节点的数据实际上是保存在本地Linux文件系统中的。每个数据节点会周期性地向名称节点发送“心跳”信息,报告自己的状态,没有按时发送心跳信息的数据节点会被标记为“宕机”,不会再给它分配任何I/O请求。

3-4

图3-4 HDFS的体系结构

用户在使用HDFS时,仍然可以像在普通文件系统中那样,使用文件名去存储和访问文件。实际上,在系统内部,一个文件会被切分成若干个数据块,这些数据块被分布存储到若干个数据节点上。当客户端需要访问一个文件时,首先把文件名发送给名称节点,名称节点根据文件名找到对应的数据块(一个文件可能包括多个数据块),再根据每个数据块信息找到实际存储各个数据块的数据节点的位置,并把数据节点位置发送给客户端,最后,客户端直接访问这些数据节点获取数据。在整个访问过程中,名称节点并不参与数据的传输。这种设计方式,使得一个文件的数据能够在不同的数据节点上实现并发访问,大大提高了数据访问速度。

HDFS采用Java语言开发,因此,任何支持JVM的机器都可以部署名称节点和数据节点。在实际部署时,通常在集群中选择一台性能较好的机器作为名称节点,其他机器作为数据节点。当然,一台机器可以运行任意多个数据节点,甚至名称节点和数据节点也可以放在一台机器上运行,不过,很少在正式部署中采用这种模式。HDFS集群中只有唯一一个名称节点,该节点负责所有元数据的管理,这种设计大大简化了分布式文件系统的结构,可以保证数据不会脱离名称节点的控制,同时,用户数据也永远不会经过名称节点,这大大减轻了中心服务器的负担,方便了数据管理。

3.4.2 HDFS命名空间管理

HDFS的命名空间包含目录、文件和块。命名空间管理是指命名空间支持对HDFS中的目录、文件和块做类似文件系统的创建、修改、删除等基本操作。在当前的HDFS体系结构中,在整个HDFS集群中只有一个命名空间,并且只有唯一一个名称节点,该节点负责对这个命名空间进行管理。

HDFS使用的是传统的分级文件体系,因此,用户可以像使用普通文件系统一样,创建、删除目录和文件,在目录间转移文件,重命名文件等。但是,HDFS还没有实现磁盘配额和文件访问权限等功能,也不支持文件的硬连接和软连接(快捷方式)。

3.4.3 通信协议

HDFS是一个部署在集群上的分布式文件系统,因此,很多数据需要通过网络进行传输。所有的HDFS通信协议都是构建在TCP/IP协议基础之上的。客户端通过一个可配置的端口向名称节点主动发起TCP连接,并使用客户端协议与名称节点进行交互。名称节点和数据节点之间则使用数据节点协议进行交互。客户端与数据节点的交互是通过RPC(Remote Procedure Call)来实现的。在设计上,名称节点不会主动发起RPC,而是响应来自客户端和数据节点的RPC请求。

3.4.4 客户端

客户端是用户操作HDFS最常用的方式,HDFS在部署时都提供了客户端。不过需要说明的是,严格来说,客户端并不算是HDFS的一部分。客户端可以支持打开、读取、写入等常见的操作,并且提供了类似Shell的命令行方式来访问HDFS中的数据(参见第3.7.1节)。此外,HDFS也提供了Java API,作为应用程序访问文件系统的客户端编程接口(参见第3.7.3节)。

3.4.5 HDFS体系结构的局限性

HDFS只设置唯一一个名称节点,这样做虽然大大简化了系统设计,但也带来了一些明显的局限性,具体如下。

(1)命名空间的限制。名称节点是保存在内存中的,因此,名称节点能够容纳对象(文件、块)的个数会受到内存空间大小的限制。

(2)性能的瓶颈。整个分布式文件系统的吞吐量,受限于单个名称节点的吞吐量。

(3)隔离问题。由于集群中只有一个名称节点,只有一个命名空间,因此,无法对不同应用程序进行隔离。

(4)集群的可用性。一旦这个唯一的名称节点发生故障,会导致整个集群变得不可用。

3.5 HDFS的存储原理

3.5.1 冗余数据的保存

作为一个分布式文件系统,为了保证系统的容错性和可用性,HDFS采用了多副本方式对数据进行冗余存储,通常一个数据块的多个副本会被分布到不同的数据节点上,如图3-5所示,数据块1被分别存放到数据节点A和C上,数据块2被存放在数据节点A和B上。这种多副本方式具有以下几个优点。

图像说明文字

图3-5 HDFS数据块多副本存储

(1)加快数据传输速度。当多个客户端需要同时访问同一个文件时,可以让各个客户端分别从不同的数据块副本中读取数据,这就大大加快了数据传输速度。

(2)容易检查数据错误。HDFS的数据节点之间通过网络传输数据,采用多个副本可以很容易判断数据传输是否出错。

(3)保证数据的可靠性。即使某个数据节点出现故障失效,也不会造成数据丢失。

3.5.2 数据存取策略

数据存取策略包括数据存放、数据读取和数据复制等方面,它在很大程度上会影响到整个分布式文件系统的读写性能,是分布式文件系统的核心内容。

1.数据存放

为了提高数据的可靠性与系统的可用性,以及充分利用网络带宽,HDFS采用了以机架(Rack)为基础的数据存放策略。一个HDFS集群通常包含多个机架,不同机架之间的数据通讯需要经过交换机或者路由器,同一个机架中不同机器之间的通讯则不需要经过交换机和路由器,这意味着同一个机架中不同机器之间的通讯要比不同机架之间机器的通讯带宽大。

HDFS默认每个数据节点都是在不同的机架上,这种方法会存在一个缺点,那就是写入数据的时候不能充分利用同一机架内部机器之间的带宽。但是,与这点缺点相比,这种方法也带来了更多很显著的优点:首先,可以获得很高的数据可靠性,即使一个机架发生故障,位于其他机架上的数据副本仍然是可用的;其次,在读取数据的时候,可以在多个机架并行读取数据,大大提高了数据读取速度;最后,可以更容易实现系统内部负载均衡和错误处理。

HDFS默认的冗余复制因子是3,每一个文件块会被同时保存到3个地方,其中,有两份副本放在同一个机架的不同机器上面,第三个副本放在不同机架的机器上面,这样既可以保证机架发生异常时的数据恢复,也可以提高数据读写性能。

2.数据读取

HDFS提供了一个API可以确定一个数据节点所属的机架ID,客户端也可以调用API获取自己所属的机架ID。当客户端读取数据时,从名称节点获得数据块不同副本的存放位置列表,列表中包含了副本所在的数据节点,可以调用API来确定客户端和这些数据节点所属的机架ID,当发现某个数据块副本对应的机架ID和客户端对应的机架ID相同时,就优先选择该副本读取数据,如果没有发现,就随机选择一个副本读取数据。

3.数据复制

HDFS的数据复制采用了流水线复制的策略,大大提高了数据复制过程的效率。当客户端要往HDFS中写入一个文件时,这个文件会首先被写入本地,并被切分成若干个块,每个块的大小是由HDFS的设定值来决定的。每个块都向HDFS集群中的名称节点发起写请求,名称节点会根据系统中各个数据节点的使用情况,选择一个数据节点列表返回给客户端,然后,客户端就把数据首先写入列表中的第一个数据节点,同时把列表传给第一个数据节点,当第一个数据节点接收到4KB数据的时候,写入本地,并且向列表中的第二个数据节点发起连接请求,把自己已经接收到的4KB数据和列表传给第二个数据节点,当第二个数据节点接收到4KB数据的时候,写入本地,并且向列表中的第三个数据节点发起连接请求,依次类推,列表中的多个数据节点形成一条数据复制的流水线。最后,当文件写完的时候,数据复制也同时完成。

3.5.3 数据错误与恢复

HDFS具有较高的容错性,可以兼容廉价的硬件,它把硬件出错看成一种常态,而不是异常,并设计了相应的机制检测数据错误和进行自动恢复,主要包括以下几种情形。

1.名称节点出错

名称节点保存了所有的元数据信息,其中,最核心的两大数据结构是FsImage和Editlog,如果这两个文件发生损坏,那么整个HDFS实例将失效。因此,HDFS设置了备份机制,把这些核心文件同步复制到备份服务器SecondaryNameNode上,备份服务器本身不会处理任何请求,只扮演备份机的角色,虽然这样会增加名称节点服务器的负担,但是,可以有效保证数据的可靠性和系统可用性。当名称节点出错时,就可以根据备份服务器SecondaryNameNode中的FsImage和Editlog数据进行恢复。

2.数据节点出错

每个数据节点会定期向名称节点发送“心跳”信息,向名称节点报告自己的状态。当数据节点发生故障,或者网络发生断网时,名称节点就无法收到来自一些数据节点的心跳信息,这时,这些数据节点就会被标记为“宕机”,节点上面的所有数据都会被标记为“不可读”,名称节点不会再给它们发送任何I/O请求。这时,有可能出现一种情形,即由于一些数据节点的不可用,会导致一些数据块的副本数量小于冗余因子。名称节点会定期检查这种情况,一旦发现某个数据块的副本数量小于冗余因子,就会启动数据冗余复制,为它生成新的副本。HDFS和其他分布式文件系统的最大区别就是可以调整冗余数据的位置。

3.数据出错

网络传输和磁盘错误等因素,都会造成数据错误。客户端在读取到数据后,会采用md5和sha1对数据块进行校验,以确定读取到正确的数据。在文件被创建时,客户端就会对每一个文件块进行信息摘录,并把这些信息写入同一个路径的隐藏文件里面。当客户端读取文件的时候,会先读取该信息文件,然后,利用该信息文件对每个读取的数据块进行校验,如果校验出错,客户端就会请求到另外一个数据节点读取该文件块,并且向名称节点报告这个文件块有错误,名称节点会定期检查并且重新复制这个块。

3.6 HDFS的数据读写过程

在介绍HDFS数据读写过程之前,需要简单介绍一下相关的类。FileSystem是一个通用文件系统的抽象基类,可以被分布式文件系统继承,所有可能使用Hadoop文件系统的代码都要使用到这个类。Hadoop为FileSystem这个抽象类提供了多种具体的实现,DistributedFileSystem就是FileSystem在HDFS文件系统中的实现。FileSystem的open()方法返回的是一个输入流FSDataInputStream对象,在HDFS文件系统中,具体的输入流就是DFSInputStream;FileSystem中的create()方法返回的是一个输出流FSDataOutputStream对象,在HDFS文件系统中,具体的输出流就是DFSOutputStream。

3.6.1 读数据的过程

客户端连续调用open()、read()、close()读取数据时,HDFS内部的执行过程如下(见图3-6)。

3-6

图3-6 HDFS读数据的过程

(1)客户端通过FileSystem.open()打开文件,相应地,在HDFS文件系统中,DistributedFileSystem具体实现了FileSystem,因此,调用open()方法后,DistributedFileSystem会创建输入流FSDataInputStream,对于HDFS而言,具体的输入流就是DFSInputStream。

(2)在DFSInputStream的构造函数中,输入流通过ClientProtocal.getBlockLocations()远程调用名称节点,获得文件开始部分数据块的保存位置。对于该数据块,名称节点返回保存该数据块的所有数据节点的地址,同时,根据距离客户端的远近对数据节点进行排序;然后,DistributedFileSystem会利用DFSInputStream来实例化FSDataInputStream,返回给客户端,同时返回了数据块的数据节点地址。

(3)获得输入流FSDataInputStream后,客户端调用read()函数开始读取数据。输入流根据前面的排序结果,选择距离客户端最近的数据节点建立连接并读取数据。

(4)数据从该数据节点读到客户端;当该数据块读取完毕时,FSDataInputStream关闭和该数据节点的连接。

(5)输入流通过getBlockLocations()方法查找下一个数据块(如果客户端缓存中已经包含了该数据块的位置信息,就不需要调用该方法)。

(6)找到该数据块的最佳数据节点,读取数据。

(7)当客户端读取完毕数据的时候,调用FSDataInputStream的close()函数,关闭输入流。

需要注意的是,在读取数据的过程中,如果客户端与数据节点通信时出现错误,就会尝试连接包含此数据块的下一个数据节点。

3.6.2 写数据的过程

客户端向HDFS写数据是一个复杂的过程,这里介绍一下在不发生任何异常的情况下,客户端连续调用create()、write()和close()时,HDFS内部的执行过程(见图3-7)。

3-7

图3-7 HDFS写数据的过程

(1)客户端通过FileSystem.create()创建文件。

相应地,在HDFS文件系统中,DistributedFile System具体实现了FileSystem,因此,调用create ()方法后,DistributedFileSystem会创建输出流对象FSDataOutputStream,对于HDFS而言,具体的输出流就是DFSOutputStream。

(2)然后,DistributedFileSystem通过RPC远程调用名称节点,在文件系统的命名空间中创建一个新的文件。名称节点会执行一些检查,比如文件是否已经存在,客户端是否有权限创建文件等。检查通过之后,名称节点会构造一个新文件,并添加文件信息。远程方法调用结束后,DistributedFileSystem会利用DFSOutputStream来实例化FSDataOutputStream,返回给客户端,客户端使用这个输出流写入数据。

(3)获得输出流FSDataOutputStream以后,客户端调用输出流的write()方法向HDFS中对应的文件写入数据。

(4)客户端向输出流FSDataOutputStream中写入的数据,会首先被分成一个个的分包,这些分包被放入DFSOutputStream对象的内部队列。输出流FSDataOutputStream会向名称节点申请保存文件和副本数据块的若干个数据节点,这些数据节点形成一个数据流管道。队列中的分包最后被打包成数据包,发往数据流管道中的第一个数据节点,第一个数据节点将数据包发送给第二个数据节点,第二个数据节点将数据包发送给第三个数据节点,这样,数据包会流经管道上的各个数据节点(即第3.5.2节介绍的流水线复制策略)。

(5)因为各个数据节点位于不同机器上,数据需要通过网络发送,因此,为了保证所有数据节点的数据都是准确的,接收到数据的数据节点要向发送者发送“确认包”(ACK Packet)。确认包沿着数据流管道逆流而上,从数据流管道依次经过各个数据节点并最终发往客户端,当客户端收到应答时,它将对应的分包从内部队列移除。不断执行(3)~(5)步,直到数据全部写完。

(6)客户端调用close()方法关闭输出流,此时开始,客户端不会再向输出流中写入数据,所以,当DFSOutputStream对象内部队列中的分包都收到应答以后,就可以使用ClientProtocol.complete()方法通知名称节点关闭文件,完成一次正常的写文件过程。

3.7 HDFS编程实践

本节介绍Linux操作系统中关于HDFS文件操作的常用Shell命令,利用Web界面查看和管理Hadoop文件系统,以及利用Hadoop提供的Java API进行基本的文件操作。

3.7.1 HDFS常用命令

在Linux命令行终端,我们可以利用Shell命令对Hadoop进行操作。利用这些命令可以完成HDFS中文档的上传、下载、复制、查看文件信息、格式化名称节点等操作。关于HDFS的Shell命令有一个统一的格式。

hadoop command [genericOptions] [commandOptions]

HDFS有很多命令,其中,fs命令可以说是HDFS最常用的命令,利用fs命令可以查看HDFS文件系统的目录结构、上传和下载数据、创建文件等。该命令的用法如下。

hadoop fs [genericOptions] [commandOptions]

具体如下。

  • hadoop fs -ls <path>。显示<path>指定的文件的详细信息。

  • hadoop fs -cat <path>。将<path>指定的文件的内容输出到标准输出(stdout)。

  • hadoop fs -chgrp [-R] group <path>。将<path>指定的文件所属的组改为group,使用-R将对<path>指定的文件夹内的文件进行递归操作。这个命令只适用于超级用户。

  • hadoop fs -chown [-R] [owner] [: [group]] <path>。改变<path>指定的文件的拥有者,-R用于递归改变文件夹内的文件的拥有者。这个命令只适用于超级用户。

  • hadoop fs -chmod [-R] <mode><path>。将<path>指定的文件的权限更改为<mode>。这个命令只适用于超级用户和文件的所有者。

  • hadoop fs -tail [-f] <path>。将<path>指定的文件最后1KB的内容输出到标准输出(stdout)上。-f选项用于持续检测新添加到文件中的内容。

  • hadoop fs -stat [format] <path>。以指定的格式返回<path>指定的文件的相关信息。当不指定format的时候,返回文件<path>的创建日期。

  • hadoop fs -touchz <path>。创建一个<path>指定的空文件。

  • hadoop fs -mkdir <path>。创建<path>指定的文件夹。

  • hadoop fs -copyFromLocal <localsrc><dst>。将本地源文件<localsrc>复制到路径<dst>指定的文件或文件夹中。

  • hadoop fs -copyToLocal [-ignorecrc] [-crc] <target><localdst>。将目标文件<target>复制到本地文件或文件夹<localdst>中。可用-ignorecrc选项复制CRC校验失败的文件。使用-crc选项复制文件以及CRC信息。

  • hadoop fs -cp <src><dst>。将文件从源路径<src>复制到目标路径<dst>。

  • hadoop fs -du <path>。显示<path>指定的文件或文件夹中所有文件的大小。

  • hadoop fs -dus <path>。显示<path>指定的文件的大小。

  • hadoop fs -expunge。清空回收站。请参考HDFS官方文档以获取更多关于回收站特性的信息。

  • hadoop fs -get [-ignorecrc] [-crc] <src><localdst>。复制<src>指定的文件到本地文件系统<localdst>指定的文件或文件夹。可用-ignorecrc选项复制CRC校验失败的文件。使用-crc选项复制文件以及CRC信息。

  • hadoop fs -getmerge <src><localdst> [addnl]。对<src>指定的源目录中的所有文件进行合并,写入<localdst>指定的本地文件。addnl是可选的,用于指定在每个文件结尾添加一个换行符。

  • hadoop fs -ls <path>。显示<path>指定的文件或目录下所有文件的信息。

  • hadoop fs -lsr <path>。ls命令的递归版本。

  • hadoop fs -mkdir <path>。创建<path>指定的文件夹。

  • hadoop fs -put <localsrc><dst>。从本地文件系统中复制<localsrc>指定的单个或多个源文件到<dst>指定的目标文件系统中。也支持从标准输入(stdin)中读取输入写入目标文件系统。

  • hadoop fs -moveFromLocal <localsrc><dst>。与put命令功能相同,但是文件上传结束后,会从本地文件系统中删除<localsrc>指定的文件。

  • hadoop fs -mv <src><dest>。将文件从源路径<src>移动到目标路径<dst>。

  • hadoop fs -rm <path>。删除<path>指定的文件。只删除非空目录和文件。

  • hadoop fs -rmr <path>。删除<path>指定的文件夹及其下的所有文件。

  • hadoop fs -setrep [-R] <path>。改变<path>指定的文件的副本系数。-R选项用于递归改变目录下所有文件的副本系数。

  • hadoop fs -test -[ezd] <path>。检查<path>指定的文件或文件夹的相关信息。不同选项的作用如下。

   -e检查文件是否存在,如果存在则返回0,否则返回1。

   -z检查文件是否是0字节,如果是则返回0,否则返回1。

   -d如果路径是个目录,则返回1,否则返回0。

  • hadoop fs -text <path>。将<path>指定的文件输出为文本格式。文件的格式允许是zip和TextRecordInputStream等。

3.7.2 HDFS的Web界面

在配置好Hadoop集群之后,可以通过浏览器登录“http://[NameNodeIP]:50070”访问HDFS文件系统,其中,[NameNodeIP]表示名称节点的IP地址。例如,我们在本地机器上完成Hadoop伪分布式安装后,可以登录“http://localhost:50070”来查看文件系统信息(见图3-8)。

通过该Web界面,我们可以查看当前文件系统中各个节点的分布信息,浏览名称节点上的存储、登录等日志,在线查看或下载某个数据节点上某个文件的内容等。该Web界面的所有功能都能通过Hadoop提供的Shell命令或者Java API来等价实现。例如,通过Web界面中的“Browse the filesystem”查看文件“hdfs://localhost/home/administrator/tempfile/file1.txt”的内容,查看结果如图3-9所示。我们也可以通过如下命令实现同样的功能。

hadoop fs -cat hdfs://localhost/home/administrator/tempfile/file1.txt

图片 30

图3-8 显示HDFS文件系统信息的Web界面

图片 31

图3-9 利用Web界面查看文件的内容

3.7.3 HDFS常用Java API及应用实例

Hadoop主要是使用Java语言编写实现的,Hadoop不同的文件系统之间通过调用Java API进行交互。上面介绍的Shell命令,本质上就是Java API的应用。这里将介绍HDFS中进行文件上传、复制、下载等操作常用的Java API及其编程实例。

1.常用Java API介绍

HDFS编程的主要Java API如下。

  • org.apache.hadoop.fs.FileSystem。一个通用文件系统的抽象基类,可以被分布式文件系统继承。所有可能使用Hadoop文件系统的代码都要使用到这个类。Hadoop为FileSystem这个抽象类提供了多种具体的实现,如LocalFileSystem、DistributedFileSystem、HftpFileSystem、FsftpFileSystem、HarFileSystem、KosmosFileSystem、FtpFileSystem, NativeS3FileSystem(参见《Hadoop权威指南》来了解更多的信息)。

  • org.apache.hadoop.fs.FileStatus。一个接口,用于向客户端展示系统中文件和目录的元数据,具体包括文件大小、块大小、副本信息、所有者、修改时间等。可通过FileSystem.listStatus()方法获得具体的实例对象。

  • org.apache.hadoop.fs.FSDataInputStream。文件输入流,用于读取Hadoop文件。

  • org.apache.hadoop.fs.FSDataOutputStream。文件输出流,用于写Hadoop文件。

  • org.apache.hadoop.conf.Configuration。访问配置项。所有的配置项的值,如果在core-site.xml中有对应的配置,则以core-site.xml为准,否则以core-default.xml中相应的配置项信息为准。

  • org.apache.hadoop.fs.Path。用于表示Hadoop文件系统中的一个文件或者一个目录的路径。

  • org.apache.hadoop.fs.PathFilter。一个接口,通过实现方法PathFilter.accept(Path path)来判定是否接收路径path表示的文件或目录。

2.应用实例

接下来通过一个简单的实例来介绍上述Java API的使用方法。图3-10显示了HDFS文件系统中,路径为“hdfs://localhost/home/administrator/tempfile”的目录中所有的文件信息。对于该目录下的所有文件,我们将执行以下操作。

图片 230

图3-10 待处理的文件信息

首先,从该目录中过滤出所有后缀名不为“.abc”的文件。

然后,对过滤之后的文件进行读取。

最后,将这些文件的内容合并到文件“hdfs://localhost/home/administrator/tempwork/merge.txt”中。

上述操作的具体实现过程如下。

(1)定义过滤器,我们将过滤掉后缀名为“.abc”的文件,这里通过实现接口org.apache.hadoop. fs.PathFilter中的方法accept(Path path),对path指代的文件进行过滤。

(2)利用FileSystem.listStatus(Path path, PathFilter filter)方法获得目录path中所有文件经过过滤器filter过滤后的状态对象数组。

(3)利用FileSystem.open(Path path)方法获得与路径path相关的FSDataInputStream对象,并利用该对象读取文件的内容。

(4)利用FileSystem.create(Path path)方法获得与路径path相关的FSDataOutputStream对象,并利用该对象将字节数组输出到文件。

(5)利用FileSystem.get(URI uri, Configuration con)方法,根据资源表示符uri和文件系统配置信息con获得对应的文件系统。

上述操作的具体代码如下。

/**
 * 过滤掉文件名满足特定条件的文件
 * @author administrator
 */
class MyPathFilter implements PathFilter {
   String reg = null; 
   MyPathFilter(String reg) {
     this.reg = reg;
   }
   @Override
   public boolean accept(Path path) {
  if (!(path.toString().matches(reg)))
   return true;
  return false;
 }
}
/***
 * 利用FSDataOutputStream和FSDataInputStream合并HDFS中的文件
 * @author administrator
 */
public class Merge {
 Path inputPath = null; //待合并的文件所在的目录的路径
 Path outputPath = null; //输出文件的路径
 public Merge(String input, String output) {
  this.inputPath = new Path(input);
  this.outputPath = new Path(output);
 }
 public void doMerge() throws IOException {
  Configuration conf = new Configuration();
  FileSystem fsSource = FileSystem.get(URI.create(inputPath.toString()), conf);
  FileSystem fsDst = FileSystem.get(URI.create(outputPath.toString()), conf);
  FileStatus[] sourceStatus = fsSource.listStatus(inputPath,
    //下面过滤掉输入目录中后缀为.abc的文件
    new MyPathFilter(".*\\.abc")); 
  FSDataOutputStream fsdos = fsDst.create(outputPath);
  //下面分别读取过滤之后的每个文件的内容,并输出到同一个文件中
  for (FileStatus sta : sourceStatus) {
   //下面打印后缀不为.abc的文件的路径、文件大小
   System.out.print("路径:" + sta.getPath() + "  文件大小:" + sta.getLen()
     + "  权限:" + sta.getPermission() + "  内容:");
   FSDataInputStream fsdis = fsSource.open(sta.getPath());
   byte[] data = new byte[1024];
   int read = -1;
   PrintStream ps = new PrintStream(System.out);
   while ((read = fsdis.read(data)) > 0) {
    ps.write(data, 0, read);
    fsdos.write(data, 0, read);
   }
   fsdis.close();
   ps.close();
  }
  fsdos.close();
 }
 public static void main(String[] args) throws IOException {
  Merge merge = new Merge(
    "hdfs://localhost/home/administrator/tempfile/",
    "hdfs://localhost/home/administrator/tempwork/merge.txt");
  merge.doMerge();
 }
}

运行上述代码后,可以在Web界面中登录“http://localhost:50070/”查看文件系统,可以发现,目录“/home/administrator/tempwork”中存在一个文件“merge.txt”;或者,在终端输入命令“hadoop fs-ls hdfs://localhost/home/administrator/tempwork”,也可以发现merge.txt这个文件。单击该文件即可查看文件的内容,具体如图3-11所示。

图片 231

图3-11 合并之后的文件信息

3.8 本章小结

分布式文件系统是大数据时代解决大规模数据存储问题的有效解决方案,HDFS开源实现了GFS,可以利用由廉价硬件构成的计算机集群实现海量数据的分布式存储。

HDFS具有兼容廉价的硬件设备、流数据读写、大数据集、简单的文件模型、强大的跨平台兼容性等特点。但是,也要注意到,HDFS也有自身的局限性,比如不适合低延迟数据访问、无法高效存储大量小文件和不支持多用户写入及任意修改文件等。

块是HDFS的核心概念,一个大的文件会被拆分成很多个块。HDFS采用抽象的块概念,具有支持大规模文件存储、简化系统设计、适合数据备份等优点。

HDFS采用了主从(Master/Slave)结构模型,一个HDFS集群包括一个名称节点和若干个数据节点。名称节点负责管理分布式文件系统的命名空间;数据节点是分布式文件系统HDFS的工作节点,负责数据的存储和读取。

HDFS采用了冗余数据存储,增强了数据可靠性,加快了数据传输速度。HDFS还采用了相应的数据存放、数据读取和数据复制策略,来提升系统整体读写响应性能。HDFS把硬件出错看成一种常态,设计了错误恢复机制。

本章最后介绍了HDFS的数据读写过程以及HDFS编程实践方面的相关知识。

3.9 习题

1.试述分布式文件系统设计的需求。

2.分布式文件系统是如何实现较高水平扩展的?

3.试述HDFS中的块和普通文件系统中的块的区别。

4.试述HDFS中的名称节点和数据节点的具体功能。

5.在分布式文件系统中,中心节点的设计至关重要,请阐述HDFS是如何减轻中心节点的负担的。

6.HDFS只设置唯一一个名称节点,在简化系统设计的同时也带来了一些明显的局限性,请阐述局限性具体表现在哪些方面。

7.试述HDFS的冗余数据保存策略。

8.数据复制主要是在数据写入和数据恢复的时候发生,HDFS数据复制是使用流水线复制的策略,请阐述该策略的细节。

9.试述HDFS是如何探测错误发生以及如何进行恢复的。

10.请阐述HDFS在不发生故障的情况下读文件的过程。

11.请阐述HDFS在不发生故障的情况下写文件的过程。

实验2 熟悉常用的HDFS操作

一、实验目的

(1)理解HDFS在Hadoop体系结构中的角色。

(2)熟练使用HDFS操作常用的Shell命令。

(3)熟悉HDFS操作常用的Java API。

二、实验平台

操作系统:Linux。

Hadoop版本:1.2.1或以上版本。

JDK版本:1.6或以上版本。

Java IDE:Eclipse。

三、实验内容和要求

1.编程实现以下指定功能,并利用Hadoop提供的Shell命令完成相同的任务。

(1)向HDFS中上传任意文本文件,如果指定的文件在HDFS中已经存在,由用户指定是追加到原有文件末尾还是覆盖原有的文件。

(2)从HDFS中下载指定文件,如果本地文件与要下载的文件名称相同,则自动对下载的文件重命名。

(3)将HDFS中指定文件的内容输出到终端。

(4)显示HDFS中指定的文件读写权限、大小、创建时间、路径等信息。

(5)给定HDFS中某一个目录,输出该目录下的所有文件的读写权限、大小、创建时间、路径等信息,如果该文件是目录,则递归输出该目录下所有文件相关信息。

(6)提供一个HDFS内的文件的路径,对该文件进行创建和删除操作。如果文件所在目录不存在,则自动创建目录。

(7)提供一个HDFS的目录的路径,对该目录进行创建和删除操作。创建目录时,如果目录文件所在目录不存在则自动创建相应目录;删除目录时,由用户指定当该目录不为空时是否还删除该目录。

(8)向HDFS中指定的文件追加内容,由用户指定内容追加到原有文件的开头或结尾。

(9)删除HDFS中指定的文件。

(10)删除HDFS中指定的目录,由用户指定目录中如果存在文件时是否删除目录。

(11)在HDFS中,将文件从源路径移动到目的路径。

2.编程实现一个类“MyFSDataInputStream”,该类继承“org.apache.hadoop.fs.FSDataInputStream”,要求如下。

(1)实现按行读取HDFS中指定文件的方法“readLine()”,如果读到文件末尾,则返回空,否则返回文件一行的文本。

(2)实现缓存功能,即利用“MyFSDataInputStream”读取若干字节数据时,首先查找缓存,如果缓存中有所需数据,则直接由缓存提供,否则向HDFS读取数据。

3.查看Java帮助手册或其他资料,用“java.net.URL”和“org.apache.hadoop.fs.FsURLStream HandlerFactory”编程来输出HDFS中指定文件的文本到终端中。

四、实验报告

《大数据技术原理与应用》 实验报告
题目: 姓名: 日期:
实验环境:

实验内容与完成情况:

出现的问题:

解决方案(列出遇到的问题和解决办法,列出没有解决的问题):


目录

相关文章

  • 山西省高等教育计算机专业课程教学研讨会

    2016年4月24日,人民邮电出版社与山西财经大学信息管理学院联合举办了“山西省本科计算机专业精品资源共享课程研讨会,山西省100多位相关专业老师参加,会上全国”大数据“与”数据结构“课程专家进行了精彩的讲座,获得参会教师的一致好评。...

    1256 0 0 6
  • 以平台化思维构建高效大数据课程公共服务体系

    大数据的兴起,将在很大程度上改变中国高校信息技术相关专业的现有教学体制和教学方法。作为大数据教学工作者,必须敢于站在第三次信息化浪潮的潮头,高瞻远瞩,大胆实践,只有这样,才能够在新时期的教学工作中有所作为,有所成就。中国高校大数据课程公共服务平台,是一个开放的平台,不断进步提升的平台,未来将会有更多来自全国各地的热爱大数据教学的开拓创新者加入这个平台,为平台建设添砖加瓦,共同推进中国高校大数据教学...

    972 0 0 1

推荐用户

同系列书

人邮微信
本地服务
人邮微信
教师服务
二维码
读者服务
读者服务
返回顶部
返回顶部