Hadoop的分布式文件系统—— Hadoop权威指南3

  • 本章除了讲解HDFS,还从整个Hadoop文件系统的角度介绍了文件系统的命令行、FileSystem接口(Hadoop文件系统的客户端接口)

1. HDFS概述

1. HDFS的特性、应用场景(适合/不适合什么?why?)
  • HDFS的是Hadoop的分布式文件系统,全称Hadoop Distributed Filesystem

HDFS具有以下特性:

  1. 超大文件:几百MB、几百GB甚至几百TB的大文件,甚至还出现了存储PB级文件的Hadoop集群
  2. 流式数据访问:HDFS基于一次写入、多次读取是高效的文件访问模式构建的。
  3. 商用硬件(普通硬件):Hadoop将运行在商用硬件集群上,节点故障将会是常态。HDFS在遇到节点故障时,能继续提供服务而不被用户感知。

HDFS不适用的场景

  1. 大量的小文件:HDFS中,文件的元数据信息存储在namenode的内存中。一般,一个文件的元数据信息为150 Byte。如果,HDFS中存在大量的小文件(如数十亿个小文件),则会超过内存的存储容量。
  2. 低延迟数据访问:HDFS专为大文件实现高吞吐,可能会以提高数据时延为代价。因此,HDFS不适合低延迟数据访问。 —— HBase是更好的选择
  3. 随机写/多用户写:因为HDFS只允许顺序写,支持单个用户写入。
1.2 HDFS中namenode和datanode
  • HDFS中的namenode是整个文件系统的管理者,负责管理文件系统的namespace。
    ① 以文件的形式永久存储namespace镜像文件和编辑日志文件
    ② 在内存中,存储block的位置信息
  • datanode:
    ① 负责block的存储和检索
    ② 同时,定期向namenode上报自己存储的block列表
  • namenode初期的容错机制:
    ① 将namenode在多个文件系统中保持元数据持久状态:写入本地磁盘和网络文件系统
    ② 使用辅助namenode,不是真正的namenode,而是定期合并namespace镜像文件和编辑日志,减轻namenode的CPU压力
    ③ 更好的方法:namenode失效时,从NFS复制到辅助namenode并作为新的namenode运行

注意:

  1. 这里的备用namenode就是常说的secondaryNameNode,从Hadoop 0.21.0版本中开始出现。也就是说,在Hadoop 1.x中,就有secondaryNameNode
  2. Hadoop 1.x版本和2.x版本,最大的区别是增加了yarn,专门用于资源管理和任务调度,使MR专注于分布式计算
  3. Hadoop 3.x没有架构上的变化,而是专注于性能优化 —— 可以参考视频:Hadoop概述–Hadoop发行版本、架构变迁
1.3 HDFS的数据块(block)
  • 磁盘有默认的数据块大小,这是磁盘进行数据读写的最小单位,如512 Byte
  • 同时,文件系统也有数据块的概念,一般是磁盘数据块的整数倍。
  • 数据块的设置对于用户来说是透明的,他们无法感知到磁盘或文件系统的数据块大小
  • HDFS也有数据块(block)的概念,不同版本的Hadoop中HDFS数据块的默认大小有变化。从Hadoop 2.7.3开始,默认的block从64MB变成了128MB

block大小如何抉择?

  1. HDFS的block大小比磁盘块大很多,这是为了最小化寻址开销。一个块足够大,则数据传输时间会明显大于磁盘寻道时间。这样,一个由多个块组成的大文件的传输时间就会取决于磁盘的传输速率
  2. map任务通常一次只处理一个block中的数据,如果block过于大,数据块将变少。map任务也变少,不利于充分发挥集群的分布式计算能力

block带来的好处

  1. block的概念使得一个超过磁盘大小的文件,可以跨机器存储
  2. 使用抽象的block作为数据存储单元,而不是使用整个文件,这样可以简化存储子系统的设计;同时,有利于将文件数据和文件元数据独立管理
  3. block还有利于数据备份,提高HDFS的容错能力

注意: 在HDFS中,虽然规定一个block为128MB。但是,如果文件末尾的数据块只有1MB,在实际存储时,它是不会真正的占据128MB的存储空间。

2. HDFS的HA(高可用性)

  • 前面有提到说,辅助namenode可以帮助namenode合并编辑日志到namespace镜像文件中,从而减轻namenode的压力。同时,可以结合NFS和辅助namenode,实现在namenode故障时,快速切换到新的namenode

  • 其实,namenode存在单点失效(SPOF)的问题,一旦namenode故障或软/硬件升级,所有的Hadoop将无法读写HDFS

  • 想要切换到新的namenode至少需要30分钟甚至更长时间。

  • 如何能将在namenode故障时,能迅速切换到新的namenode,保证Hadoop服务无任何明显的中断?

    Hadoop 2针对这个问题,为HDFS提供了高可用性的支持。将提供服务namenode叫做active namenode,而为实现HA作备份的namenode叫做standby namenode

  • active namenode:在任何时候都处于active状态,负责处理所有客户端操作

  • standby namenode:充当工作人员角色的同时,拥有足够多的状态信息,能在时刻提供快速的故障转移

2.1 QJM实现HDFS的HA
  • Quorum Journal Manager,群体日志管理器,专为HDFS实现,可以提供一个HA的编辑日志。
  • 基于QJM的HA集群,需要两种节点:
    ① namenode节点:配置完全相同、运行active和standby namenode的节点
    ② 日志节点:用于运行JournalNode进程的节点,由于journalNode是相对轻量级,因此它可以在其他的Hadoop节点上运行。例如,运行namenode、resourceManager、jobTracker的上

注意

  1. 至少应该有3个日志节点,对编辑日志的更新要求必须写入了大多数日志节点
  2. 也就是说,日志节点可以容忍至多 ( N − 1 ) / 2 (N-1)/2 (N1)/2的故障

基于NFS的HA以及这两种HA的实现方式,后续通过专门的专题进行学习

3. Hadoop的文件系统

  • 我们一说到Hadoop,总会提到HDFS,但是Hadoop不止有HDFS这一种文件系统
  • Hadoop有一个抽象的文件系统概念,提供了fs.FileSystem接口,可以用于实现各种不同的文件系统
  • Hadoop中已经实现的文件系统有:Local(本地文件系统)、HDFS、WebHDFS(基于http的文件系统)、S3(由Amazon S3支持的文件系统)、Swift(由OpenStack Swift支持的文件系统)等
3.1 文件系统命令行
  • Hadoop不仅基于文件系统接口实现了各种文件系统,它还提供了操作文件系统的丰富命令
  • 这些命令以hadoop fs开头,可以操作各种各样的Hadoop文件系统,包括本地文件系统、S3、WebHDFS等
  • 完整的命令可以参考官网:FileSystem Shell
  • 同时,还有专门针对HDFS的命令行,其中hdfs dfs专门用于操作HDFS文件系统

hadoop fs与hdfs dfs命令的区别与联系?

  • hadoop fs可以操作各种文件系统,包括HDFS,而hdfs dfs专门用于操作HDFS
  • 当通过hadoop fs操作HDFS时,与hdfs dfs命令效果是一样的
3.2 通过文件系统API实现HDFS文件的常见操作
3.2.1 读取HDFS文件到标准输出流
  • 代码示例如下:

    public class ReadHdfsFile {
        public static void main(String[] args) throws IOException {
            // 获取hdfs文件路径
            String uri = args[0];
    
            // 获取基于hdfs的文件系统
            Configuration conf = new Configuration();
            FileSystem fileSystem = FileSystem.get(URI.create(uri), conf);
    
            // 创建文件输入流
            InputStream in = null;
            try {
                in = fileSystem.open(new Path(uri));
    
                // 使用工具类读取hdfs文件到标准输出流
                IOUtils.copyBytes(in, System.out, 4096, false);
            }finally {
                IOUtils.closeStream(in);
            }
        }
    }
    
  • 运行程序:注意hdfs的路径需要查看集群的core-site.xml中fs.defaultFS的配置,否则容易出现Call From xxxx to hadoop:8020 failed on connection exception: java.net.ConnectException: Connection refused的异常

    hadoop jar original-hadoop-file-operate-1.0-SNAPSHOT.jar com/lucy/hadoop/file/operate/ReadHdfsFile hdfs://hadoop:9000/user/hadoop/output/score_2/part-r-00000
    
  • 运行结果:
    在这里插入图片描述

关于FSDataOutputStream

  • FileSystemopen()方法,返回了一个FSDataOutputStream对象,即为文件创建了一个输入流

  • FSDataOutputStream类的声明如下:

    public class FSDataInputStream extends DataInputStream 
    	implements Seekable, PositionedReadable, ByteBufferReadable, HasFileDescriptor, CanSetDropBehind, CanSetReadahead, HasEnhancedByteBufferAccess, CanUnbuffer, StreamCapabilities, ByteBufferPositionedReadable 
    
  • 重点是Seekable接口和PositionedReadable接口

  • Seekable接口有两个方法:
    seek()方法可以设置从文件的哪个位置开始读,使得FSDataInputStream支持随机读
    seek()方法与InputStreamskip()方法不同,前者基于文件开头位置的offset偏移处开始读,后者是从当前位置offset偏移处开始读
    getPos()可以查询当前位置与文件开头的offset偏移

    void seek(long var1) throws IOException;
    long getPos() throws IOException;
    
  • PositionedReadable接口:
    read()方法:从指定的position处开始读取至多为length字节的文件,同时从缓冲区buffer的offset偏移处开始存储数据流。如果读到了length字节的数据,或者读至文件末尾,都会自动停止并返回实际读取的字节数
    readFully()方法:与 read()方法参数相同,但是对文件末尾的处理不同。readFully()读至文件末尾时,仍不够length字节,则会抛出EOFException

    read(long position, byte[] buffer, int offset, int length)
    void readFully(long position, byte[] buffer, int offset, int length) throws IOException;
    void readFully(long position, byte[] buffer) throws IOException;
    
3.2.2 将本地文件写入HDFS
  • 代码示例如下:

    public class WriteHdfsFile {
        public static void main(String[] args) throws URISyntaxException, IOException {
            String hdfsPath = args[0];
            String localPath = args[1];
    
            // 获取文件系统
            Configuration conf = new Configuration();
            FileSystem fileSystem = FileSystem.get(new URI(hdfsPath), conf);
    
            // 创建本地文件输入流
            InputStream in = new BufferedInputStream(new FileInputStream(localPath));
    
            // 创建hdfs文件输出流
            // 打印读取进度
            OutputStream out = fileSystem.create(new Path(hdfsPath), () -> System.out.println("."));
            IOUtils.copyBytes(in, out, 4096, false);
    
            // 关闭文件流
            IOUtils.closeStream(out);
            IOUtils.closeStream(in);
        }
    }
    
    
  • 运行命令

    hadoop jar hadoop-file-operate-1.0-SNAPSHOT.jar com/lucy/hadoop/file/operate/WriteHdfsFile hdfs://hadoop:9000/user/hadoop/output/write/local.txt data.txt
    
  • 运行出错了,后面再说吧 😂

    Exception in thread "main" java.io.FileNotFoundException: hdfs://hadoop:9000/user/hadoop/output/write/local.txt (No such file or directory)
    
  • 关于create()方法:
    ① 它会默认自动创建文件不存在的父目录;如果想要在父目录不存在的情况下,禁止文件写入,可以先调用exists()方法,判断父目录是否存在
    ② 第二个参数是Progressable,用于传递回调接口,可以将数据的写入进度通知给应用
    ③ 其创建的是FSDataOutputStream输出流,它只有getPos()方法,没有设置offset的方法。这样的设计,与HDFS只允许顺序写相照应。

3.2.3 其他常见操作
  • 获取文件的元数据信息:listStatus()方法
  • 创建目录:mkdirs()方法,会自动创建父目录及当前目录
  • 删除目录/文件:delete()方法,可以通过指定参数,决定是否递归删除目录下的所有文件及子目录。
3.3 HDFS的文件访问权限
  • 通过hdfs dfs -ls path,得到目录信息如下

    drwxrwxr-x+  - cms_presto hive          0 2021-04-07 10:40 dm_insight_crowd_upload_df/data_id=104
    -rwxrwxr-x+  2 hdfs supergroup        181 2021-04-09 17:42 dm_insight_crowd_upload_df/20210409_094246_08136_x5rhx_a843b61b-45df-498f-b5b8-a6272b5e204c.gz
    
  • 可以看到HDFS的目录列表与linux中的目录列表格式基本是一致的,只是第二列的数值:linux的hard links

    drwxr-xr-x 19 sunrise sunrise      4096 Jan  8 14:33 Python-3.7.2
    -rwxrwxrwx  1 sunrise sunrise  22897802 Dec 24  2018 Python-3.7.2.tgz
    

通过红框(截图内容的文件名不完整)对HDFS的文件信息进行讲解:

  1. 第一列,是文件模式。第一个字符,为d表示这是一个目录,为-表示这是一个文件;后面是owner权限、group权限和其他用户权限,每三位为分隔。
  2. 第二列,是文件的默认副本数;如果是目录,则无副本的概念,使用-表示;如果是文件,则是文件的副本数,截图中的文件副本数是2
  3. 第三列,是目录/文件的owner,即所属用户
  4. 第四列,是目录/文件的group
  5. 第五列,是文件的size,以字节为单位;目录的size为0,截图中的文件大小为181字节
  6. 第六七列,是文件的最近一次的修改时间
  7. 第八列,是目录/文件名
    在这里插入图片描述
  • 最近也是被业务的读写权限问题搞疯了,找了好几个运维都治标不治本
    ① 为整个/database目录设置了用户xxx的acl权限,允许xxx读写+执行
    ② 每次用户新建了表,目录为/database/table1,总是提示没有读写权限
    ③ 运维A说,给数据库设置了acl,目录下的表时会自动继承这个acl的;运维B说,你还是每次用户新建表以后,用这几条命令去给他们加权限吧
    ④ 还是我们组的大神(之前做运维,现在是开发为主,运维为辅)告诉了我真正的原因:业务通过presto创建的表,是不会自动继承父目录的acl的

4. 总结

  • 说先是对HDFS的一个认识吧:HDFS适合什么场景(超大文件、流式文件访问、商用硬件)?不适合什么场景(低延迟访问、多用户写入/随机写、小文件存储)?why?

  • HDFS的两种节点:
    ① namenode(namespace管理)、datanode(文件存储/检索、block信息上报)
    ② namenode的容错机制:NFS、辅助namenode(只是负责编辑日志与namespace镜像文件的合并,减轻namenode压力);或者NFS + 辅助namenode

  • namenode的HA:
    ① namenode存在单点故障,Hadoop 2提供对HDFS的HA支持:提出了active namenodestandby namenode
    active namenode:任意时刻处于active状态,接受客户端操作;standby namenode:拥有足够多的状态信息,可以提供快速的故障转移
    ③ 通过QJM(群体日志管理器),HDFS专用的日志编辑器,要求至少有3个节点,支持 ( n − 1 ) / 2 (n-1)/2 (n1)/2的节点故障

  • Hadoop的文件系统:
    ① 提供一个抽象的文件系统概念,FileSystem是Hadoop的抽象文件接口
    ② 基于FileSystem实现了多种文件系统:Local、HDFS、webHDFS、S3等
    ③ 文件系统的命令:hadoop fs,以及专门针对HDFS的hdfs dfs
    ④ 通过FileSystem进行简单的HDFS文件操作:读写、创建目录、删除文件/目录、获取元数据信息
    hdfs dfs -ls,查看文件系统的相关信息(文件权限、修改时间、副本数等)

  • HDFS文件的读写:
    FSDataInputStream实现了Seekable接口和PositionedReadable接口,从而支持随机读
    FSDataOutputStream,只有getPos()方法,不支持随机写

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值