HDFS是一种文件系统,专为MapReduce这类框架下的大规模分布式数据处理而设计。你可以把一个大数据集(比如说100TB)在HDFS中存储为单个文件,而大多数其他的文件系统无力实现这一点。HDFS使你不必考虑这些细节,让你感觉就像在处理单个文件一样。
因为HDFS并不是一个天生的Unix文件系统,不支持像ls和cp这种标准的Unix文件命令,也不支持如fopen()和fread()这样的标准文件读写操作。另一方面,Hadoop确也提供了一套与linux文件命令类似的命令行工具。
注意 一个典型的Hadoop工作流会在别的地方生成数据库文件(如日志文件)再将其复制到HDFS中。接着由MapReduce程序处理这个数据,但它们通常不会直接读任何一个HDFS文件。相反,它们依靠MapReduce框架来读取HDFS文件,并将之解析为独立的记录(键值对),这些记录才是MapReduce程序所处理的数据单元。除非需要定制数据的导入与导出,否则你几乎不必编程来读写HDFS文件。
基本文件命令
Hadoop的文件命令采取的形式为
hadoop fs -cmd <args>
其中cmd是具体的文件命令,而是一组数目可变的命令参数cmd命令通常与UNIX对应的命令名相同。例如,文件列表命令为
hadoop fs -ls
让我们来看看在Hadoop中最常用文件管理任务,其中包括
添加文件和目录
获取文件
删除文件
指定文件盒目录确切位置的URI
Hadoop的文件命令既可以与HDFS文件系统交互,也可以和本地文件系统交互。URI精确地定位到一个特定文件或目录的位置。完整的URI格式为scheme://authority/path
。Scheme类似于一个协议。它可以是hdfs或file,来分别指定HDFS文件系统或本地文件系统。对于HDFS,authority是NameNode的主机名,而path是文件或目录的路径。例如,对于在本地机器的9000端口号上,以标准伪分布式模型运行的HDFS,访问用户目录user/chuck中文件example.txt的URI大致为hdfs://localhost:9000/user/chuck/example.txt
.
你可以使用hadoop的cat命令来显示该文件的内容:
hadoop fs -cat hdfs://localhost:9000/usr/chuck/example.txt
正如我们马上就会看到的,大多数设置不需要指定URI中的scheme://authority部分。对于本地文件系统,你可能会更喜欢用标准的Unix命令,而不是hadoop文件命令。当在本地文件系统和HDFS之间复制文件时,hadoop中的命令(如put和get)会分别把本地文件系统作为源和目的,而不需要制定scheme为file。对于其他命令,如果未设置URI中scheme://authority,就会采用Hadoop的默认配置。例如,假如conf/core-site.xml文件已经更改为伪分布式配置,则文件中fs.default.name属性应为
<property>
<name>fs.default.name</name>
<value>hdfs://localhost:9000</value>
</property>
在此配置下,URIhdfs://localhost:9000/user/chuck/example.txt
缩短为/user/chuck/example.txt
。显示文件内容的hadoo cat命令可以写为hadoop fs -cat example.txt
添加文件和目录
在运行hadoop程序处理存储在HDFS上的数据之前,你需要首先把数据放在HDFS上。让我们假设你已经完成了格式化,并启动了一个HDFS文件系统(出于学习目的,我们建议使用伪分布式模式的配置。)让我们创建一个目录并放入一个文件。
HDFS有一个默认的工作目录/usr/
hadoop fs -mkdir /user/chuck
hadoop的mkdir命令会自动创建父目录(如果此前不存在的话),类似于UNIX中使用-p选项的mkdir命令,因此上述命令还会创建/user目录,让我们用ls命令对目录进行检查:
hadoop fs -ls/
该命令返回根目录下目录/user的信息
Found 1 items
drwxr-xr-x -chuck supergroup 0 2009-01-14 10:23 /user
如果你想看到所有的子目录,则可以使用Hadoop的lsr命令,它类似于Unix中打开-r选项的ls:hadoop fs -lsr /
你会看到所有文件和子目录:
drwxr-xr-x -chuck supergroup 0 2009-01-14 10:23 /user
drwxr-xr-x -chuck supergroup 0 2009-01-14 10:23 /user/chuck
既然有了一个工作目录,我们可以放一个文件进去。在本地文件系统中创建一个名为example.txt的文本文件,用hadoop的命令put将它从本地文件系统复制到HDFS中去。
hadoop fs -put example.txt.
注意上面的这个命令最后一个参数是句点(.)。这意味着我们把文件放入了默认的工作目录。该命令等价于:
hadoop fs -put example.txt /user/chuck
我们重新执行递归列出文件的命令,可以看到新的文件被添加到HDFS中。
$hadoop fs -lsr /
drwxr-xr-x -chuck supergroup 0 2009-01-14 10:23 /user
drwxr-xr-x -chuck supergroup 0 2009-01-14 11:02 /user/chuck
-rw-r--r-- 1 chuck supergroup 264 2009-01-14 11:02 /user/chuck/example.txt
实际上,我们并不需要递归地检查所有文件,而仅限于在我们自己工作目录中的文件。我们可以通过最简单的形式来使用Hadoop的ls命令:
$ hadoop fs -ls
Found 1 items
-rw-r--r-- 1 chuck supergroup 264 2009-01-14 11:02 /user/chuck/exampe.txt
输出结果显示出属性信息,比如权限、所有者、组、文件大小,以及最后修改日期,所有这些都类似于Unix的概念。显示“1”的列给出文件的复制因子。伪分布式配置下它永远为1。对于生产环境中的集群,复制因子通常为3,但也可以是任何正整数。因为复制因子不适用于目录,故届时该列会显示一个破折号(-)。
当你把数据放到HDFS上之后,你可以运行Hadoop程序来处理它。处理过程将输出一组新的HDFS文件,然后你可以读取或检索这些结果。
检索文件
hadoop的get命令与put截然相反。它从HDFS中复制文件到本地文件系统。比方说,我们在本地不再拥有文件example.txt,而想从HDFS中将它取回,我们就可以运行命令:
hadoop fs -get example.txt.
将它复制到我们在本地的当前工作目录中。
另一种访问数据的方式是显示。由hadoop的cat命令来支持:
hadoop fs -cat example.txt
我们可以在Hadoop的文件命令中使用Unix的管道,将其结果发送给其他的Unix命令做进一步处理。例如,如果该文件非常大(正如典型的Hadoop文件那样),并且你希望快速地检查其内容,就可以把hadoop中cat命令的输出用管道传递给Unix命令head:
hadoop fs -cat example.txt|head
hadoop内在支持tail命令来查看最后一千字节:
hadoop fs -tail example.txt
文件在HDFS上的任务完成之后,可以考虑删除它们以释放空间。
删除文件
hadoop删除文件的命令为rm,现在你也许不会感到太惊讶了:
hadoop fs -rm example.txt
rm命令还可以用于删除空目录。
编程读写HDFS
为了体验HDFS的Java API,我们将开发一个PutMerge程序,用于合并文件后放入HDFS。命令行工具并不支持这个操作,我们将使用API来实现。
需要分析来自许多Web服务器的Apache日志文件时,就有了建立这个历程的动机。虽然我们可以把每个日志文件都复制到HDFS中,但通常而言,Hadoop处理单个大文件会比处理许多个小文件更有效率(这里“小”是相对的,因为它仍会达到几十或几百GB)。此外,从分析目的来看,我们把日志数据视为一个大文件。日志数据被分散在多个文件是由于Web服务器采用分布式架构所带来的副作用。一种解决办法是先将所有的文件合并,然后再复制到HDFS。可是,文件合并需要占用本地计算机的大量磁盘空间。如果我们能够在向HDFS复制的过程中合并它们,事情就会简单很多。
因此,我们需要一个PutMerge类型的操作。hadoo命令行中有一个getmerge命令,用于一组HDFS文件在复制到本地以前进行合并。但我们想要的截然相反,故无法再Hadoop的文件管理工具中获得。我们用HDFS API来自己编程实现它。
在Hadoop中用作文件操作的主流位于org.apache.hadoop.fs软件包中。Hadoop的基本文件操作包括常见的open、read、write和close。实际上,hadoop的文件API是通用的,可以用于HDFS以外的其他文件系统。对于我们的PutMerge程序,它读取本地文件系统和写入HDFS都会使用Hadoop的文件api。
不多说,直接上代码:
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.eclipse.jdt.internal.compiler.ConfigurableOption;
public class PutMerge
{
public static void main(String[] args) throws IOException
{
Configuration conf = new Configuration();
FileSystem hdfs = FileSystem.get(conf);
FileSystem local = FileSystem.getLocal(conf);
Path inputDir = new Path(args[0]);//1.设定输入目录与输出文件
Path hdfsFile = new Path(args[1]);
try
{
FileStatus[] inputFiles = local.listStatus(inputDir);//2.得到本地文件列表
FSDataOutputStream out = hdfs.create(hdfsFile);//3.生成HDFS文件流
for (int i = 0; i < inputFiles.length; i++)
{
System.out.println(inputFiles[i].getPath().getName());
FSDataInputStream in = local.open(inputFiles[i].getPath());//4.打开本地输入流
byte buffer[] = new byte[256];
int bytesRead = 0;
while ((bytesRead = in.read(buffer)) > 0)
{
out.write(buffer, 0, bytesRead);
}
in.close();
}
out.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
我们已经讨论了如何处理HDFS中的文件,你现在知道一些方法来读写HDFS中的数据,但是仅仅有数据还不够,还要对它进行处理分析以及其他的操作。在后续学习过程中,我们将介绍Hadoop的另一个主要组件——MapReduce框架,看看如何基于它来编程。