1. 分布式文件系统,即为管理网络中跨多台计算机存储的文件系统。HDFS以流式数据访问模式来存储超大文件,运行于商用硬件集群上。HDFS的构建思路为:一次写入、多次读取是最高效的访问模式。数据集通常由数据源生成或从数据源赋值而来,接着长时间在此数据集上进行各类分析。每次分析都涉及该数据集的大部分数据甚至全部,因此读取整个数据集的时间延迟比第一条记录的时间延迟更重要。
2. HDFS是为高数据吞吐量应用优化的,这可能会以高时间延迟为代价。目前,对于低延迟的访问需求,HBase是更好的选择。
3. HDFS的块默认为64M,与单一磁盘上的文件系统相似,HDFS上 的文件也被划分为块大小的多个分块(chunk),作为独立的存储单元。不同的是HDFS中小于一个块大小的文件不会占据整个块的空间。HDFS的块比磁盘大,目的是为了最小化寻址开销。如果块设置得足够大,从磁盘传输数据的时间可以明显大于定位这个块开始位置所需的时间。这样,传输一个由多个块组成的文件的时间取决于磁盘的传输速率。
4. 对分布式文件系统中的块进行抽象会带来很多好处。1) 一个文件的大小可以大于网络中任意一个磁盘的容量;2) 使用块抽象而非整个文件作为存储单元,大大简化了存储子系统的设计。3) 块非常适合用于数据备份进而提供数据容错能力和可用性。将每个块复制到少数几个独立的机器上(默认为3个),可以确保在发生块、磁盘或机器故障后数据不丢失。如果发现一个块不可用,系统会从其他地方读取另一个副本,而这个过程对用户是透明的。一个因损坏或机器故障而丢失的块可以从其他候选地点复制到另一台可以正常运行的机器上,以保证复本的数量回到正常水平。HDFS中的fsck指令可以显示块的信息。% hadoop fsck / -files -blocks将列出文件系统中各个文件由哪些块构成。
5. HDFS集群有两类节点,并以管理者-工作者模式运行,即一个namenode(管理者)和多个datanode(工作者).
namonode管理文件系统的命名空间。它维护着文件系统树及整棵树内所有的文件和目录。这些信息以两个文件形式永久保存在本地磁盘上:命名空间镜像文件和编辑日志文件。namenode也记录着每个文件中各个块所在的数据节点信息,但它不会永久保存块的位置信息。因为这些信息会在系统启动时由数据节点重建。
客户端代表用户通过与namenode和datanode交互来访问整个文件系统。
datanode是文件系统的工作节点。它们根据需要存储并检索数据块(受客户端或namenode调度),并且定期向namenode发送它们所存储的块的列表。
6. 没有namenode,文件系统将无法使用,因此对namenode实现容错非常重要,Hadoop为此提供了两种机制:
备份那些组成文件系统元数据持久状态的文件。Hadoop可通过配置使namenode在多个文件系统上保存元数据的持久状态,这些写操作是实时同步的,一般的配置是,将持久状态写入本地磁盘的同时,写入一个远程挂载的网络文件系统(NFS)。
运行一个辅助namenode,但它不能被用作namenode。其作用是定期通过编辑日志并命名空间镜像,以防止编辑日志过大,且一般在另一台单独的物理计算机上运行。它会保存合并后的命名空间镜像的副本,并在namenode发生故障时启用。但,辅助namenode保存的状态总是滞后于主节点,所以在主节点全部失效时,难免会丢失部分数据。在这种情况下,一般把存储在NFS上的namenode元数据复制到辅助namenode并作为新的主namenode运行。
7. 基本文件系统操作
可以通过hadoop fs -help命令获取所有命令的详细帮助文件。
(1) 从本地文件系统将一个文件复制到HDFS。
%hadoop fs -copyFromLocal input/docs/quangle.txt hdfs://localhost/user/tom/quangle.txt
该命令调用Hadoop文件系统的shell命令fs,本地文件quangle.txt被复制到运行在localhost上的HDFS实例中,路径为/user/tom/quangle.txt。事实上,可以简化命令格式以省略主机的URI并使用默认设置,即省略hdfs://localhost,因为该项已在core-site.xml中指定。
%hadoop fs -copyFromLocal input/docs/quangle.txt /user/tom/quangle.txt
也可以使用相对路径,并将文件复制到HDFS的home目录中,本例中为/usr/tom
%hadoop fs -copyFromLocal input/docs/quangle.txt quangle.txt
我们将文件复制回本地文件系统,并检查是否一致:
% hadoop fs -copyToLocal quangle.txt quangle.copy.txt
% md5 input/docs/quangle.txt quangle.copy.txt
由于MD5键值相同,表明这个文件在HDFS之旅中得以幸存并保存完整。
(2) 创建一个目录看它zai列表中是如何显示的:
% hadoop fs -mkdir books
% hadoop fs -ls .
目录作为元数据保存在namenode中,而非datanode中。
8. Hadoop文件系统
HDFS只是Hadoop的一个实现。Java抽象类org.apache.hadoop.fs.FileSystem定义了Hadoop中的一个文件系统接口。
文件系统 | URI方案 | Java实现 | 描述 |
Local | file | fs.LocalFileSystem | 使用了客户端检验和的本地磁盘文件系统。没有使用校验和的本地磁盘文件系统RawLocalFileSystem |
HDFS | hdfs | hdfs.DistributedFileSystem | Hadoop的分布式文件系统,将HDFS设计成与MapReduce结合使用,可以实现高性能 |
HFTP | Hftp | hdfs.hftpFileSystem | 一个在HTTP上提供对HDFS只读访问的文件系统,通常与distcp结合使用,以实现并运行不同版本的HDFS的集群之间复制数据 |
HSFTP | hsftp | hdfs.HsftpFileSystem | 在HTTPS上提供对HDFS只读访问的文件系统 |
HAR | har | fs.HarFileSystem | 一个构建在其他文件系统之上用于文件存档的文件系统。Hadoop存档文件系统通常用于需要将HDFS中的文件进行存档时,以减少namenode内存的使用 |
hfs(云存储) | kfs | fs.kgs.kosmosFileSystem | CloudStore是类似于HDFS或是谷歌的GFS文件系统 |
FTP | ftp | fs.ftp.FTPFileSystem | 由FTP服务器支持的文件系统 |
s3(原生) | s3n | fs.s3native.NativeS3FileSystem | 由Amazon S3支持的文件系统 |
s3(基于块) | s3 | fs.sa.S3FileSystem | 由Amazon S3支持的文件系统,以块格式存储文件以解决S3的5GB文件大小限制 |
一般使用URI方案来选取合适的文件系统实例进行交互。例:要想列出本地文件系统根目录下的文件,输入命令:
%hadoop fs file:///
9 Hadoop文件系统的接口是通过Java API提供的,所以其他非Java应用程序访问Hadoop文件系统会比较麻烦。thriftfs定制功能模块中的Thrift API通过把Hadoop文件系统包装一个Apache Thrift服务来弥补这个不足,从而使任何具有Thrift绑定的语言都能轻松地与Hadoop文件系统进行交互。
10 Hadoop提供了一个名为libhdfs的C语言库,该语言库是Java FileSystem接口类的一个镜像。它可以使用Java原生接口(JNI)调用Java文件系统客户端。
11 用户空间文件系统(Filesystem in Userspace,FUSE)允许把按照用户空间实现的文件系统整合成一个Unix文件系统,通过使用Hadoop的Fuse-DFS功能模块,任意一个Hadoop文件系统均可以作为一个标准文件系统进行挂载。
12 Java接口:与Hadoop的某一文件系统进行交互的API。
(1) 从Hadoop URL中读取数据
最简单的方法是使用java.net.URL对象打开数据流,进而从中读取数据。
例:通过URLStreamHandler实例以标准输出方式显示Hadoop文件系统的文件
public class URLCat{
static {
// JVM只能调用一次该方法,因此第三方组件如已声明一个URLStreamHandlerFactory实例
// 将无法再使用该方法从Hadoop中读取数据
URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
}
public static void main(String[] args) throws Exception{
InputStream in = null;
try{
in = new URL(args[0]).openStream();
// 输入流和输出流之间复制数据
// 4096-设置复制的缓冲区大小,flase-设置复制结束后是否关闭数据流
IOUtils.copyBytes(in,System.out,4096,false);
}finally{
IOUtils.closeStream(in);
}
}
}
下面是一个运行示例:
% hadoop URLCat hdfs://localhost/user/tom/quangle.txt
(2) 通过FileSystem API读取数据
Hadoop文件系统中通过Hadoop Path对象来代表文件,你可以将一条路径视为一个Hadoop文件系统URI。
获取FileSystem实例有两种方法:
public static FileSystem get(Configuration conf) throws IOException
public static FileSystem get(URI uri,Configuration conf) throws IOException
Configuration对象封装了客户端或服务器的配置,通过设置配置文件读取类路径来实现(如conf/core-site.xml)。第一个方法返回的是默认文件系统,第二个方法通过给定的URI方案和权限来确定要使用的文件系统。
有了FileSystem实例后,可调用open函数来读取文件的输入流:
public FSDataInputStream open(Path f) throws IOException
public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException
例:直接使用FileSystem以标准格式显示Hadoop文件系统中的文件
public class FileSystemCat{
public static void main(String[] args) throws IOExcption{
String uri = args[0];
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri),conf);
InputStream in = null;
try{
in = fs.open(new Path(uri));
IOUtils.copyBytes(in,System.out,4096,false);
}finally{
IOUtils.closeStream(in);
}
}
}
运行示例:
% hadoop FileSystemCat hdfs://localhost/user/tom/quangle.txt
FSDataInputStream对象,该类继承了java.io.DataInputStream接口,并支持随机访问,由此可从流的任意位置读取数据。
package org.apache.hadoop.fs;
public class FSDataInputStream extends DataInputStream
implements Seekable,PositionedReadable{
}
Seekable接口支持在文件中找到指定位置,并提供一个查询当前位置相对于文件起始位置偏移量的查询方法getPos()。
public interface Seekable{
void seek(long pos) throws IOException;
long getPos() throws IOException;
boolean seekToNewSource(long targetPos) throws IOException;
}
调用seek()定位大于文件长度的位置会导致IOException异常,与java.io.InputStream中的skip()不停,seek()可以移到文件中任意一个绝对位置,skip()则只能相对于当前位置定位到另一个新位置。
将一个文件写入标准输出两次:在第一次写完之后,定位到文件的起始位置再次以流方式读取该文件。
public class FileSystemDoubleCat{
String uri = args[0];
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri),conf);
FSDataInputStream in = null;
try{
in = fs.open(new Path(uri));
IOUtils.copyBytes(in,System.out,4096,false);
in.seek(0);
IOUtils.copyBytes(in,System.out,4096,false);
}finally{
IOUtils.closeStream(in);
}
}
运行示例:
% hadoop FileSystemDoubleCat hdfs://local/user/tom/quangle.txt
,从一个指定偏移量处读取文件的一部分:
public interface PositionedReadable{
public int read(long position, byte[] buffer, int offset, int length) throws IOException;
public void readFully(long position, byte[] buffer, int offset, int length) throws IOException;
public void readFully(long position, byte[] bufer) throws IOException;
}
read()方法从文件的指定position处读取至多为length字节的数据并存入缓冲区buffer指定的偏移量offset处。返回的是实际读到的字节数。
所有这些方法会保留当前偏移量,因此它们提供了在读取文件-可能是元数据-的主体时访问文件的其他部分的便利方法。
13 写入数据
给准备创建的文件指定一个Path对象,然后返回一个用于写入数据的输出流。
public FSDataOutputStream create(Path f) throws IOException;
Progressable,用于传递回调接口,如此一来,可以把数据写入数据节点的进度通知到你的应用:
package org.apache.hadoop.util;
public interface Progressable{
public void progress();
}
使用append()方法在一个已有文件末尾追加数据:
public FSDataOutputStream append(Path f) throws IOException
追加操作允许一个writter打开文件后在访问文件的最后偏移量处追加数据。该API可以时某些应用创建无边界文件。
例:显示如何将本地文件复制到Hadoop文件系统,每次Hadoop调用progress()方法时,打印一个时间点来显示整个运行过程。
public class FileCopyWithProgress{
public static void main(String[] args) throws IOException{
String localSrc = args[0];
String dst = args[1];
InputStream in = new BufferedInputStream(new FileInputStream(localSrc));
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(dst),conf);
OutputStream out = fs.create(new Path(dst),new Progressable(){
public void progress(){
System.out.print(".");
}
});
IOUtils.copyBytes(in,out,4096,true);
}
}
运行示例:
% hadoop FileCopyWithProgress input/docs/1400-8.txt hdfs://localhost/user/tom/1400-8.txt
FSDataOutputStream也有一个查询文件当前位置的方法:
package org.apache.hadoop.fs;
public class FSDataOutputStream extends DataOutputStream
implements Syncable{
public long getPos() throws IOException{
}
}
FSDataOutputStream类不允许在文件中定位,因为HDFS只允许对一个已打开的文件顺序写入或在现有文件的末尾追加数据。
14 FileSystem实例提供了创建目录的方法:
boolean mkdirs(Path f) throws IOException
该方法可以一次性新建所有必要但还没有的父目录,如果目录都已创建成功,则返回true。