- 本章除了讲解HDFS,还从整个Hadoop文件系统的角度介绍了文件系统的命令行、FileSystem接口(Hadoop文件系统的客户端接口)
1. HDFS概述
1. HDFS的特性、应用场景(适合/不适合什么?why?)
- HDFS的是Hadoop的分布式文件系统,全称
Hadoop Distributed Filesystem
HDFS具有以下特性:
- 超大文件:几百MB、几百GB甚至几百TB的大文件,甚至还出现了存储PB级文件的Hadoop集群
- 流式数据访问:HDFS基于一次写入、多次读取是高效的文件访问模式构建的。
- 商用硬件(普通硬件):Hadoop将运行在商用硬件集群上,节点故障将会是常态。HDFS在遇到节点故障时,能继续提供服务而不被用户感知。
HDFS不适用的场景
- 大量的小文件:HDFS中,文件的元数据信息存储在namenode的内存中。一般,一个文件的元数据信息为
150 Byte
。如果,HDFS中存在大量的小文件(如数十亿个小文件),则会超过内存的存储容量。 - 低延迟数据访问:HDFS专为大文件实现高吞吐,可能会以提高数据时延为代价。因此,HDFS不适合低延迟数据访问。 —— HBase是更好的选择
- 随机写/多用户写:因为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运行
注意:
- 这里的备用namenode就是常说的
secondaryNameNode
,从Hadoop 0.21.0版本中开始出现。也就是说,在Hadoop 1.x中,就有secondaryNameNode
了 - Hadoop 1.x版本和2.x版本,最大的区别是增加了yarn,专门用于资源管理和任务调度,使MR专注于分布式计算
- Hadoop 3.x没有架构上的变化,而是专注于性能优化 —— 可以参考视频:Hadoop概述–Hadoop发行版本、架构变迁
1.3 HDFS的数据块(block)
- 磁盘有默认的数据块大小,这是磁盘进行数据读写的最小单位,如512 Byte
- 同时,文件系统也有数据块的概念,一般是磁盘数据块的整数倍。
- 数据块的设置对于用户来说是透明的,他们无法感知到磁盘或文件系统的数据块大小
- HDFS也有数据块(
block
)的概念,不同版本的Hadoop中HDFS数据块的默认大小有变化。从Hadoop 2.7.3开始,默认的block从64MB
变成了128MB
block大小如何抉择?
- HDFS的block大小比磁盘块大很多,这是为了最小化寻址开销。一个块足够大,则数据传输时间会明显大于磁盘寻道时间。这样,一个由多个块组成的大文件的传输时间就会取决于磁盘的传输速率。
- map任务通常一次只处理一个block中的数据,如果block过于大,数据块将变少。map任务也变少,不利于充分发挥集群的分布式计算能力。
block带来的好处
- block的概念使得一个超过磁盘大小的文件,可以跨机器存储
- 使用抽象的block作为数据存储单元,而不是使用整个文件,这样可以简化存储子系统的设计;同时,有利于将文件数据和文件元数据独立管理。
- 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的上
注意
- 至少应该有3个日志节点,对编辑日志的更新要求必须写入了大多数日志节点
- 也就是说,日志节点可以容忍至多 ( N − 1 ) / 2 (N-1)/2 (N−1)/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
-
FileSystem
的open()
方法,返回了一个FSDataOutputStream
对象,即为文件创建了一个输入流 -
FSDataOutputStream
类的声明如下:public class FSDataInputStream extends DataInputStream implements Seekable, PositionedReadable, ByteBufferReadable, HasFileDescriptor, CanSetDropBehind, CanSetReadahead, HasEnhancedByteBufferAccess, CanUnbuffer, StreamCapabilities, ByteBufferPositionedReadable
-
重点是
Seekable
接口和PositionedReadable
接口 -
Seekable
接口有两个方法:
①seek()
方法可以设置从文件的哪个位置开始读,使得FSDataInputStream
支持随机读
②seek()
方法与InputStream
的skip()
方法不同,前者基于文件开头位置的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的文件信息进行讲解:
- 第一列,是文件模式。第一个字符,为
d
表示这是一个目录,为-
表示这是一个文件;后面是owner权限、group权限和其他用户权限,每三位为分隔。 - 第二列,是文件的默认副本数;如果是目录,则无副本的概念,使用
-
表示;如果是文件,则是文件的副本数,截图中的文件副本数是2 - 第三列,是目录/文件的owner,即所属用户
- 第四列,是目录/文件的group
- 第五列,是文件的size,以字节为单位;目录的size为0,截图中的文件大小为181字节
- 第六七列,是文件的最近一次的修改时间
- 第八列,是目录/文件名
- 最近也是被业务的读写权限问题搞疯了,找了好几个运维都治标不治本
① 为整个/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 namenode
和standby namenode
②active namenode
:任意时刻处于active状态,接受客户端操作;standby namenode
:拥有足够多的状态信息,可以提供快速的故障转移
③ 通过QJM(群体日志管理器),HDFS专用的日志编辑器,要求至少有3个节点,支持 ( n − 1 ) / 2 (n-1)/2 (n−1)/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()
方法,不支持随机写