Hadoop学习笔记:分布式文件系统

         管理网络中跨多台计算机存储的文件系统称为分布式文件系统,Hadoop自带HDFS(Hadoop Distributed Filesystem)分布式文件系统。

 

一、HDFS设计

         HDFS以流式数据访问模式来存储超大文件,运行于商用硬件集群上。

         超大文件:几百MB、GB、TB,目前已有PB级。

         流式数据访问:一次写入、多次读取;数据集通常由数据源生成或从数据源复制而来,接着长时间在此数据集上进行各种分析。

         商用硬件:节点故障的几率较高,被设计成能够持续运行且不让用户觉察到明显中断。

         低时间延迟的数据访问:HDFS是为高数据吞吐量应用优化的,可能会以提高时间延迟为代价。

         大量的小文件:由于namenode将文件系统的元数据存储在内存中,因此该文件系统所能存储的文件总数受限于namenode的内存容量。

         多用户写入,任意修改文件:HDFS文件只支持单个写入者,而且写操作总是以“只添加”方式在文件末尾写数据。不支持多个写入者的操作,也不支持在文件的任意位置进行修改。

 

二、HDFS概念

1、数据块

         每个磁盘都有默认的数据块大小,这是磁盘进行数据读/写的最小单位。构建于单个磁盘之上的文件系统通过磁盘块来管理该文件系统中的块,该文件系统块的大小可以是磁盘块的整数倍。文件系统块一般为几千字节,而磁盘块一般为512字节。

         HDFS也有块的概念,默认为128MB。HDFS上的文件也被分为块大小的多个分块(chunk),作为独立的存储单元,HDFS中小于一个块大小的文件不会占据整个块的空间。

         HDFS的块比磁盘的块大,目的是为了最小化寻址开销。如果块足够大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间。因而,传输一个由多个块组成的大文件的时间取决于磁盘传输速率。

         对分布式文件系统中的块进行抽象的好处:① 一个文件的大小可以大于网络中任意一个磁盘的容量,文件的所有块并不需要存储在同一个磁盘上;② 大大简化了存储子系统的设计;③ 块适合于数据备份进而提供数据容错能力和提高可用性,将每个块复制到少数几个物理上相互独立的机器上(默认为3个),可以确保在块、磁盘或机器发生故障后数据不会丢失,一个因损坏或机器故障而丢失的块可以从其他候选地点复制到另一台可以正常运行的机器上,以保证复本的数量回到正常水平。

2、namenode和datanode

         HDFS集群由两类节点以管理节点-工作节点模式运行,即一个namenode(管理节点)和多个datanode(工作节点)。

namenode管理文件系统的命令空间,它维护着文件系统树及整棵树内所有的文件和目录,这些信息以两个文件形式永久保存在本地磁盘上:命名空间镜像文件和编辑日志文件。namenode也记录着每个文件中各个块所在的数据节点信息,但它不永久保存块的位置,因为这些信息会在系统启动时根据数据节点信息重建。

  datanode是文件系统的工作节点。它们需要存储并检索数据块,并且定期向namenode发送它们所存储的块的列表。

  没有namenode,文件系统将无法使用。Hadoop提供了两种namenode容错机制:① 备份组成文件系统元数据持久状态的文件,一般配置是将持久状态写入本地磁盘的同时,写入一个远程挂载的网络文件系统(NFS);② 运行一个辅助namenode,但它不能被用作namenode,辅助namenode作用是定期合并并编辑日志与命名空间镜像,以防止编辑日志过大。但是,辅助namenode保存的状态总是滞后于主节点,所以在主节点全部失效时,难免会丢失部分数据。在这种情况下,一般把存储在NFS上的namenode元数据复制到辅助namenode并作为新的主namenode运行。

3、块缓存

  通常datanode从磁盘中读取块,但对于访问频繁的文件,其对应的块可能被显式地缓存在datanode的内存中,以堆外块缓存的形式存在。作业调度器通过在缓存块的datanode上运行任务,可以利用块缓存的优势提高读操作的性能。用户或应用通过在缓存池中增加一个cache directive来告诉namenode需要缓存哪些文件以及存多久。缓存池是一个用于管理缓存权限和资源使用的管理性分组。

4、联邦HDFS

  namenode在内存中保存文件系统中每个文件和每个数据块的引用关系,这意味着对于一个拥有大量文件的超大集群来说,内存将成为限制系统横向扩展的瓶颈。在2.x发行版本中引入的联邦HDFS允许系统通过添加namenode实现扩展,其中每个namenode管理文件系统命名空间的一部分。

  在联邦环境下,每个namenode维护一个命名空间卷,由命名空间的元数据和一个数据块池组成,数据块池包含该命名空间下文件的所有数据块。命名空间卷之间是相互独立的,两两之间并不相互通信,数据块池不再进行切分。

  要访问联邦HDFS集群,客户端需要使用客户端挂载数据表将文件路径映射到namenode。该功能可以通过ViewFileSystem和viewfs://URI进行配置和管理。

5、HDFS的高可用性

  Hadoop2针对namenode失效恢复的问题增加了HA支持,配置了一对活动-备用namenode。当活动namenode失效,备用namenode就会接管它的任务并开始服务于来自客户端的请求,不会有任何明显的中断。目标实现需要做以下修改:

    1) namenode之间需要通过高可用共享存储实现编辑日志的共享;

    2) datanode需要同时向两个namenode发送数据块处理报告;

    3) 辅助namenode的角色被备用namenode所包含,备用namenode为活动的namenode命名空间设置周期性检查点。

  有两种高可用性共享存储:NFS过滤器或群体日志管理器(QJM)。QJM以一组日志节点的形式运行,每一次编辑必须写入多数日志节点。

  系统中有一个故障转移控制器的新实体,管理着将活动namenode转移为备用namenode的转换过程。有多种故障转移控制器,但默认的一种是使用了ZooKeeper来确保有且仅有一个活动namenode。每一个namenode运行着一个轻量级的故障转移控制器,其工作就是监视宿主namenode是否失效并在namenode失效时进行故障切换。

  但在非平稳故障转移的情况下,无法确切知道失效namenode是否已经停止运行。高可用实现做了更进一步的优化,以确保先前活动的namenode不会执行危害系统并导致系统崩溃的操作,该方法称为规避。

  同一时间QJM仅允许一个namenode向编辑日志中写入数据。规避机制包括:撤销namenode访问共享存储目录的权限、通过远程管理命令屏蔽相应的网络端口。诉诸的最后手段是,通过一个特定的供电单元对相应主机进行断电操作。

 

三、命令行接口

         在设置伪分布式配置时,有两个属性项需要注意。第一项是fs.defaultFS,设置为hdfs://localhost/,用于设置Hadoop的默认文件系统。第二项是dfs.replication,设为1,HDFS就不会按默认设置将文件系统复本设为3.在单独一个datanode上运行时,HDFS无法将块复制到3个datanode上,所以会持续给出复本不足的警告。设置这个属性之后,上述问题就不会再出现了。

         文件系统的基本操作:读取文件、移动文件、删除数据、列出目录等。

         从本地文件系统将一个文件复制到HDFS:

                   hadoop fs –copyFromLocal input/docs/test.txt \ hdfs://localhost/user/tom/test.txt

         其中,可以省略hdfs://localhost,因为该项已经在core-site.xml中指定。也可以使用相对路径,并将文件复制到HDFS的home目录中:

                   hadoop fs -copyFromLocal input/docs/test.txt test.txt

         将文件复制回本地文件系统,并检查是否一致:

                   hadoop fs –copyToLocal test.txt test.copy.txt

                   md5 input/docs/test.txt test.copy.txt

         MD5键值相同,表明这个文件在HDFS之旅中得以幸存并保持完整。

         新建目录,查看HDFS文件列表:

                   hadoop fs –mkdir books

                   hadoop fs –ls

         返回结果与Unix命令ls –l输出结果类似,有一些差别。第一列显式文件模式。第2列是这个文件的备份数。第3列和第4列显式文件的所属用户和组别。第5列是文件的大小,以字节为单位,目录为0。第6列和第7列是文件的最后修改日期和时间。第8列是文件或目录的名称。

 

四、Hadoop文件系统

         Java抽象类org.apache.hadoop.fs.FileSystem定义了Hadoop中一个文件系统的客户端接口,并且该抽象类有几个具体实现,其中与Hadoop紧密相关的有Local(file-使用客户端校验和的本地磁盘文件系统)、HDFS(hdfs-Hadoop分布式文件系统)、WebHDFS(Webhdfs-基于HTTP的文件系统)、SecureWebHDFS(swebhdfs-WebHDFS的HTTPS版本)、HAR(har-构建在其他文件系统之上用于文件存档的文件系统)、View(viewfs-针对其他Hadoop文件系统的客户端挂载表,通常用于为联邦namenode创建挂载点)、FTP(ftp-FTP服务器支持的文件系统)、S3(S3a-Amazon S3支持的文件系统)、Azure(wasb-Microsoft Azure支持的文件系统)、Swift(swift-OpenStack Swift支持的文件系统)。

         [上述文件系统中均依照 文件系统(URI方案-描述) 的格式]

         Hadoop一般使用URI方案来选取合适的文件系统实例进行交互。

         Hadoop以Java API的形式提供文件系统访问接口,非Java开发的应用访问HDFS会很不方便。Hadoop提供了其他一些文件系统接口的支持:

         1) HTTP

         HTTP接口比原生的Java客户端要慢,尽量不要用来传输特大数据。通过HTTP访问HDFS有两种方法:直接访问,HDFS守护进程直接服务于来自客户端的HTTP请求;通过代理访问,客户端通常使用DistributedFileSystem API访问HDFS。

第一种情况中,namenode和datanode内嵌的web服务器作为WebHDFS的端节点运行。文件元数据操作由namenode管理,文件读(写)操作首先被发往namenode,由namenode发送一个HTTP重定向至某个客户端,指示以流方式传输文件数据的目的或源datanode。

第二种方法依靠一个或者多个独立代理服务器通过HTTP访问HDFS。所有到集群的网络通信都需要经过代理,因此客户端从来不直接访问namenode和datanode。使用代理服务器后可以使用更严格的防火墙策略和带宽限制策略。通常情况下都通过代理服务器,实现在不同数据中心部署的Hadoop集群之间的数据传输,或从外网访问云端运行的Hadoop集群。

  2) C语言

  Hadoop提供了一个名为libhdfs的C语言库,该语言库是Java FileSystem接口类的一个镜像。它使用Java原生接口(JNI)调用Java文件系统客户端。同样还有一个libwebhdfs库,该库使用了WebHDFS接口。

  3) NFS

  使用Hadoop的NFSv3网关将HDFS挂载为本地客户端的文件系统。

  4) FUSE

  用户空间文件系统(FUSE,FileSystem in Userspace)允许将用户空间实现的文件系统作为Unix文件系统进行集成。

 

五、Java接口

1、从Hadoop URL读取数据

         从Hadoop文件系统读取文件,最简单的方法是使用java.net.URL对象打开数据流,从中读取数据。为使Java程序能够识别Hadoop的hdfs URL方案,还需要通过FsUrlStreamHandlerFactory实例调用java.net.URL对象的setURLStreameHandlerFactory()方法。每个Java虚拟机只能调用一次这个方法,因此通常在静态方法中调用。这个限制意味着如果程序的其他组件已经声明一个URLStreamHandlerFactory实例,你将无法使用这种方法从Hadoop中读取数据。

         public class URLCat {

                   static {

                            URL.setURLStreamHandler(new FsUrlStreamHandlerFactory());

      }

 

      public static void main(String[] args) throws Exception {

              InputStream in = null;

              try {

                       in = new URL(args[0].openStream());

                       IOUtils.copyBytes(in, System.out, 4096, false);

         } finally {

               IOUtils.closeStream(in);

         }

      }

    }

    copyBytes方法最后两个参数,第一个设置用于复制的缓冲区大小,第二个设置复制结束后是否关闭数据流。

2、通过FileSystem API读取数据

  Hadoop文件系统中通过Hadoop Path对象来代表文件,可以将路径视为一个Hadoop文件系统URI。

  1) 获取FileSystem实例:

    public static FileSystem get(Configuration conf) throws IOException

    public static FileSystem get(URI uri, Configuration conf) throws IOException

    public static FileSystem get(URI uri, Configuration conf, String user) throws IOException

  Configuration对象封装了客户端或服务器的配置,通过设置配置文件读取类路径来实现。

  2) 获取本地文件系统的运行实例

    public static LocalFileSystem getLocal(Configuration conf) throws IOException

  3) 获取文件的输入流

    public FSDataInputStream open(Path f) throws IOException  --默认缓冲大小4KB

    public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException

  FSDataInputStream对象是继承了java.io.DataInputStream的一个特殊类,支持随机访问,可以从流的任意位置读取数据。

  3、写入数据

  FileSystem新建文件的方法:给准备建的文件指定一个Path对象,然后返回一个用于写入数据的输出流:

    public FSDataOutputStream create(Path f) throws IOException

  此方法由多个重载版本,允许指定需要强制覆盖现有的文件、文件备份数量、写入文件时所用缓冲区大小、文件块大小以及文件权限。

  另一种新建文件的方法是使用append()方法在一个现有文件末尾追加数据:

    public FSDataOutputStream append(Path f) throws IOException

  FSDataOutputStream和FSDataInputStream类相似,也有一个查询文件当前位置的方法。但与FSDataInputStream类不同的是,FSDataOutputStream类不允许在文件中定位。这是因为HDFS只允许对一个已打开的文件顺序写入,或在现有文件的末尾追加数据。

4、目录

    public boolean mkdirs(Path f) throws IOException

  这个方法可以一次性新建所有必要但还没有的父目录。

5、查询文件系统

  1) 文件元数据

    FileSystem的getFileStatus()方法用于获取文件或目录的FileStatus对象。FileStatus封装了文件系统中文件和目录的元数据,包括文件长度、块大小、复本、修改时间、所有者以及权限信息。

  2) 列出文件

         listStatus()方法可以列出目录中的内容:

           public FileStatus[] listStatus(Path f) throws IOException

              public FileStatus[] listStatus(Path f, PathFilter filter) throws IOException

              public FileStatus[] listStatus(Path f[] files) throws IOException

            public FileStatus[] listStatus(Path[] files, PathFilter filter) throws IOException

          它的重载方法允许使用PathFilter来限制匹配的文件和目录。

         3) 文件模式

    可以通过通配来匹配多个文件:

           public FileStatus[] globStatus(Path pathPattern) throws IOException

           public FileStatus[] globStatus(Path pathPattern, PathFilter filter) throws IOException

        globStatus()方法返回路径格式与指定模式匹配的所有FileStatus对象组成的数组,并按路径排序。

         通配符及其含义:

通配符

名称

匹配

*

星号

匹配0或多个字符

?

问号

匹配单一字符

[ab]

字符类

匹配{a, b}集合中的一个字符

[^ab]

非字符类

匹配非{a, b}集合中的一个字符

[a-b]

字符范围

匹配一个在{a, b}范围内的字符(包括ab),a在字典顺序上要小于或等于b

[^a-b]

非字符范围

匹配一个不在{a, b}范围内的字符(包括ab),a在字典顺序上要小于或等于b

{a, b}

或选择

匹配包含a或b中的一个表达式

\c

转义字符

匹配元字符c

         4) PathFilter对象

           PathFilter对象用来排除一个特定的文件。

         5) 删除数据

           使用delete()方法可以永久性删除文件或目录。

             public boolean delete(Path f, boolean recursive) throws IOException

           如果f是一个文件或空目录,那么recursive的值就会被忽略。只有在recursive值为true时,非空目录及其内容才会被删除。

 

六、数据流

1、图解文件读取

   

 

  ① 客户端调用FileSystem对象的open()方法来打开希望读取的文件,对于HDFS来说,这个对象是DistributedFileSystem的一个实例。

  ② DistributedFileSystem通过使用远程过程调用(RPC)来调用namenode,以确定文件起始块的位置。对于每一个块,namenode返回存有该块副本的datanode地址,这些datanode根据它们与客户端的距离来排序。DistributedFileSystem类返回一个FSDataInputStream对象给客户端以便读取数据。FSDataInputStream类转而封装DFSInputStream对象,该对象管理着datanode和namenode的I/O。

  ③ 客户端对这个输入流调用read()方法。

  ④ 存储着文件起始几个块的datanode地址的DFSInputStream随即连接距离最近的文件中第一个块所在的datanode。通过对数据流反复调用read()方法,可以将数据从datanode传输到客户端。

  ⑤ 到达块的末端时,DFSInputStream关闭与该datanode的连接,然后寻找下一个块的最佳datanode。客户端从流中读取数据时,块是按照打开DFSInputStream与datanode新建连接的顺序读取的。它也会根据需要询问namenode来检索下一批数据块的datanode位置。

  ⑥ 一旦客户端完成读取,就对FSDataInputStream调用close()方法。

  在读取数据的时候,如果DFSInputStream在与datanode通信时遇到错误,会尝试从这个块的另外一个最邻近datanode读取数据。它也会记住那个故障datanode,以保证以后不会反复读取该节点上后续的块。DFSInputStream也会通过校验和确认从datanode发来的数据是否完整。如果发现有损坏的块,DFSInputStream会试图从其他datanode读取其复本,也会将损坏的块通知给namenode。

2、图解文件写入

   

         ① DistributedFileSystem对象调用create()来新建文件。

         ② DistributedFileSystem对namenode创建一个RPC调用,在文件系统的命名空间中新建一个文件,此时该文件中还没有相应的数据块。

③ DistributedFileSystem向客户端返回一个FSDataOutputStream对象,由此客户端开始写入数据。

         ④ 在客户端写入数据时,DFSOutputStream将它分成一个个的数据包,并写入内部队列,称为“数据队列”。DataStreamer将数据包流式传输到管线中第1个datanode,该datanode存储数据包并将它发送到管线中的第2个datanode。同样,第2个datanode存储该数据包并且发送给第3个datanode(最后一个)。

         ⑤ DFSOutputStream也维护着一个内部数据包来等待datanode的收到确认回执,称为“确认队列”。收到管道中所有datanode确认信息后,该数据包才会从确认队列删除。

         ⑥ 客户端完成数据的写入后,对数据流调用close()。

         ⑦ 该操作将剩余的所有数据包写入datanode管线,并在联系到namenode告知其文件写入完成之前,等待确认。

         Hadoop默认复本布局策略:在运行客户端的节点上放第1个复本(如果客户端运行在集群之外,就随即选择一个节点,不过系统会避免挑选哪些存储太满或太忙的节点);第2个复本放在与第1个不同且随即另外选择的机架中节点上。第3个复本与第2个复本放在同一个机架上,且随机选择另一个节点。其他复本放在集群中随机选择的节点上,不过系统会尽量避免在同一个的机架上放太多复本。一旦选定复本的放置位置,就根据网络拓扑创建一个管线。

3、一致模型

         文件系统的一致模型描述了文件读/写的数据可见性。新建一个文件之后,它能在文件系统的命名空间中立即可见。但是,写入文件的内容不能保证立即可见,即使数据流已经刷新并存储。

当写入的数据超过一个块后,第一个数据块对新的reader就是可见的。之后的块也不例外。总之,当前正在写入的块对其他reader不可见。

HDFS提供了一种强行将所有缓存刷新到datanode中的手段,即对FSDataOutputStream调用hflush()方法。hflush()不保证datanode已经将数据写到磁盘上,仅确保数据在datanode的内存中。为确保数据写入到磁盘上,可以用hsync()替代。

七、通过distcp并行复制

         Distcp的一种用法是替代hadoop fs –cp。例如,可以将文件复制到另一个文件中:

                   hadoop distcp file1 file2

         也可以复制目录:

                   hadoop distcp dir1 dir2

         如果dir2不存在,将新建dir2,目录dir1的内容全部复制到dir2下。可以指定多个源路径,所有源路径下的内容都将被复制到目标路径下。

         如果dir2已经存在,目录dir1将被复制到dir2下,形成目录结构dir2/dir1。如果这不是你所需的,你可以补充使用-overwrite选项,在保持同样的目录结构的同时强制覆盖原文件。你也可以使用-update选项,仅更新发生变化的文件。

         distcp是作为一个MapReduce作业来实现的,该复制作业是通过集群中并行运行的map来完成。每个文件通过一个map进行复制,并且distcp试图为每一个map分配大致相等的数据来执行。

         关于distcp的一个常用使用实例是在两个HDFS集群间传送数据:

                   hadoop distcp –update –delete –p hdfs://namenode1/foo hdfs://namenode2/foo

         -delete选项使得distcp可以删除目标路径中任意没在源路径出现的文件或目录,-p选项意味着文件状态属性如权限、块大小和复本数被保留。

         如果两个集群运行的是HDFS的不兼容版本,可以将webhdfs协议用于它们之间的distcp。另一个变种是使用HttpFs代理作为distcp源或目标,这样具有设置防火墙和控制带宽的优点。

猜你喜欢

转载自www.cnblogs.com/fantastic-clouds/p/10329514.html