- 大数据:从基础理论到最佳实践
- 祁伟
- 9146字
- 2021-01-07 18:48:01
3.1 HDFS接口与编程
HDFS提供了多种用户操作和编程接口,既通过Shell命令管理文件与目录、管理作业调度、控制与优化集群性能等,也提供了Java、C语言等的编程接口,用户可以通过编写程序对HDFS进行扩展。
3.1.1 Shell命令
HDFS资源URI的格式如下:
scheme://authority/path
其中scheme是协议名,一般是file或hdfs; authority是授权访问的主机名或IP; path是访问路径。例如:
hdfs://localhost:9000/user/chunk/test.txt
如果已经在core-site.xml里配置了fs.default.name=hdfs://localhost:9000,则仅使用/user/chunk/test.txt即可。
在HDFS的所有接口中,Shell命令行接口最简单,也是开发者比较熟悉的方式。我们通过使用“hdfs -help”命令,可以看到HDFS支持的文件系统命令,如图3-1所示。
图3-1 HDFS支持的文件系统命令
HDFS支持的文件系统命令主要有两类。
(1)用户命令:用于管理HDFS日常操作,如dfs、fsck、fetchdt等。
(2)系统管理命令:主要用于控制和管理HDFS集群,如balancer、namenode、datanode、dfsadmin、secondarynamenode等。限于篇幅,这里只介绍几种常用的命令模块。
1. hdfs dfs [GENERIC_OPTIONS][COMMAND_OPTIONS]
“hdfs dfs”提供了类似于Linux Shell一样的命令集,其用法与Linux Shell基本一致。下面详细介绍各个命令。
(1)appendToFile。
说明:将一个或者多个本地文件追加到目的文件。成功返回0,错误返回1。
格式:hdfs dfs -appendToFile <localsrc> ... <dst>
示例:
hdfs dfs -appendToFile localfile /user/hadoop/hadoopfile hdfs dfs -appendToFile localfile1 localfile2 /user/hadoop/hadoopfile hdfs dfs -appendToFile localfile hdfs://nn.example.com/hadoop/hadoopfile
(2)cat。
说明:将路径指定文件的内容输出到stdout。成功返回0,错误返回-1。
格式:hdfs dfs -cat URI [URI ...]
示例:
hdfs dfs -cat hdfs://nn1.example.com/file1 hdfs://nn2.example.com/file2 hdfs dfs -cat file:///file3 /user/hadoop/file4
(3)chgrp。
说明:改变文件所属的用户组。如果使用-R选项,则这一操作对整个目录结构递归执行。使用这一命令的用户必须是文件的所属用户,或者是超级用户。
格式:hdfs dfs -chgrp [-R]GROUP URI [URI ...]
(4)chmod。
说明:改变文件的权限。使用-R将使改变在目录结构下递归进行。命令的使用者必须是文件的所有者或者超级用户。
格式:hdfs dfs -chmod [-R]<MODE[, MODE]... | OCTALMODE> URI [URI ...]
(5)chown。
说明:改变文件的所属用户。如果使用-R选项,则这一操作对整个目录结构递归执行。使用这一命令的用户必须是文件在命令变更之前的所属用户,或者是超级用户。
格式:hdfs dfs -chown [-R][OWNER][:[GROUP]]URI [URI ]
(6)copyFromLocal。
说明:从本地复制,与put命令相似,但限定源路径是本地的。
格式:hdfs dfs -copyFromLocal <localsrc> URI
(7)copyToLocal。
说明:复制到本地,与get命令相似,但限定目的路径是本地的。
格式:hdfs dfs -copyToLocal [-ignorecrc][-crc]URI <localdst>
(8)count。
说明:计算文件、目录的数量。成功返回0,错误返回-1。
格式:hdfs dfs -count [-q][-h]<paths>
示例:
hdfs dfs -count hdfs://nn1.example.com/file1 hdfs://nn2.example.com/file2 hdfs dfs -count -q hdfs://nn1.example.com/file1 hdfs dfs -count -q -h hdfs://nn1.example.com/file1
(9)cp。
说明:将文件从源路径复制到目标路径。这个命令允许有多个源路径,但同时,目标路径必须是一个目录。成功返回0,错误返回-1。
格式:hdfs dfs -cp [-f][-p | -p[topax]]URI [URI ...]<dest>
示例:
hdfs dfs -cp /user/hadoop/file1 /user/hadoop/file2 hdfs dfs -cp /user/hadoop/file1 /user/hadoop/file2 /user/hadoop/dir
(10)du。
说明:显示目录中所有文件的大小,或者当只指定一个文件时,显示此文件的大小。成功返回0,错误返回-1。
格式:hdfs dfs -du [-s][-h]URI [URI ...]
示例:
hdfs dfs -du /user/hadoop/dir1 /user/hadoop/file1 hdfs://nn.example.com/user/hadoop/dir1
(11)dus。
说明:显示文件的大小。此命令可以用“du -s”替代。
格式:hdfs dfs -dus <args>
(12)expunge。
作用:清空回收站。
格式:hdfs dfs -expunge
(13)get。
说明:复制文件到本地文件系统。可用“-ignorecrc”选项复制CRC校验失败的文件。使用“-crc”选项复制文件以及CRC信息。成功返回0,错误返回-1。
格式:hdfs dfs -get [-ignorecrc][-crc]<src> <localdst>
示例:
hdfs dfs -get /user/hadoop/file localfile hdfs dfs -get hdfs://nn.example.com/user/hadoop/file localfile
(14)getfacl。
说明:显示文件或者目录的权限控制列表。成功返回0,错误返回非零值。
格式:hdfs dfs -getfacl [-R]<path>
示例:
hdfs dfs -getfacl /file hdfs dfs -getfacl -R /dir
(15)getfattr。
说明:显示文件或者目录的扩展属性。成功返回0,错误返回非零值。
格式:hdfs dfs -getfattr [-R]-n name | -d [-e en]<path>
示例:
hdfs dfs -getfattr -d /file hdfs dfs -getfattr -R -n user.myAttr /dir
(16)getmerge。
说明:接受一个源目录和一个目标文件作为输入,并且将源目录中所有的文件连接成本地目标文件。addnl是可选的,用于指定在每个文件结尾添加一个换行符。
格式:hdfs dfs -getmerge <src> <localdst> [addnl]
(17)ls。
说明:与Linux中一样,返回子目录或子文件列表。成功返回0,错误返回-1。
格式:hdfs dfs -ls [-R]<args>
示例:
hdfs dfs -ls /user/hadoop/file1
(18)lsr。
说明:ls命令的递归版本,一般使用“ls -R”代替。
格式:hdfs dfs -lsr <args>
(19)mkdir。
说明:创建目录,加-p选项创建多层目录。成功返回0,错误返回-1。
格式:hdfs dfs -mkdir [-p]<paths>
示例:
hdfs dfs -mkdir /user/hadoop/dir1 /user/hadoop/dir2 hdfs dfs -mkdir hdfs://nn1.example.com/user/hadoop/dir hdfs://nn2.example.com/user/hadoop/dir
(20)moveFromLocal。
说明:类似put,区别在于put操作完成后删除。
格式:hdfs dfs -moveFromLocal <localsrc> <dst>
(21)mv。
说明:将文件从源路径移动到目标路径。这个命令允许有多个源路径,此时,目标路径必须是一个目录。不允许在不同的文件系统间移动文件。成功返回0,错误返回-1。
格式:hdfs dfs -mv URI [URI ...]<dest>
示例:
hdfs dfs -mv /user/hadoop/file1 /user/hadoop/file2 hdfs dfs -mv hdfs://nn.example.com/file1 hdfs://nn.example.com/file2 hdfs://nn.example.com/file3 hdfs://nn.example.com/dir1
(22)put。
说明:从本地文件系统中复制单个或多个源路径到目标文件系统。也支持从标准输入设备中读取输入,写入目标文件系统。成功返回0,错误返回-1。
格式:hdfs dfs -put <localsrc> ... <dst>
示例:
hdfs dfs -put localfile /user/hadoop/hadoopfile hdfs dfs -put localfile1 localfile2 /user/hadoop/hadoopdir hdfs dfs -put localfile hdfs://nn.example.com/hadoop/hadoopfile
(23)rm。
说明:删除指定的文件或目录。成功返回0,错误返回-1。
格式:hdfs dfs -rm [-f][-r|-R][-skipTrash]URI [URI ...]
示例:
hdfs dfs -rm hdfs://nn.example.com/file /user/hadoop/emptydir
(24)rmr。
说明:rm的递归版本,已过时,一般使用“rm -r”代替。
格式:hdfs dfs -rmr [-skipTrash]URI [URI ...]
(25)setfacl。
说明:设置文件或者目录的权限控制列表。成功返回0,错误返回非零值。
格式:hdfs dfs -setfacl [-R][-b|-k -m|-x <acl_spec> <path>]|[--set <acl_spec> <path>]
示例:
hdfs dfs -setfacl -m user:hadoop:rw- /file hdfs dfs -setfacl -x user:hadoop /file hdfs dfs -setfacl -b /file hdfs dfs -setfacl -k /dir hdfs dfs -setfacl --set user::rw-, user:hadoop:rw-, group::r--, other::r-- /file hdfs dfs -setfacl -R -m user:hadoop:r-x /dir hdfs dfs -setfacl -m default:user:hadoop:r-x /dir
(26)setfattr。
说明:设置文件或者目录的扩展属性。成功返回0,错误返回非零值。
格式:hdfs dfs -setfattr -n name [-v value]| -x name <path>
示例:
hdfs dfs -setfattr -n user.myAttr -v myValue /file hdfs dfs -setfattr -n user.noValue /file hdfs dfs -setfattr -x user.myAttr /file
(27)setrep。
说明:改变文件和目录的复制因子。成功返回0,错误返回-1。
格式:hdfs dfs -setrep [-R][-w]<numReplicas> <path>
示例:
hdfs dfs -setrep -w 3 /user/hadoop/dir1
(28)stat。
说明:返回指定路径的统计信息。成功返回0,错误返回-1。
格式:hdfs dfs -stat URI [URI ...]
示例:
hdfs dfs -stat path
(29)tail。
说明:将文件尾部1KB的内容输出到stdout。成功返回0,错误返回-1。
格式:hdfs dfs -tail [-f]URI
示例:
hdfs dfs -tail pathname
(30)test
说明:检查文件。选项“-e”检查文件是否存在,如果存在则返回0;选项“-z”检查文件是否为0字节,如果是则返回0;选项“-d”检查路径是否为目录,如果是则返回1,否则返回0。
格式:hdfs dfs -test -[ezd]URI
示例:
hdfs dfs -test -e filename
(31)text。
说明:将源文件输出为文本格式。允许的格式是zip和TextRecordInputStream。
格式:hdfs dfs -text <src>
(32)touchz。
说明:创建一个空文件。成功返回0,错误返回-1。
格式:hdfs dfs -touchz URI [URI ...]
示例:
hdfs dfs -touchz pathname
小提示
“hadoop dfs”与“hdfs dfs”都是操作HDFS文件系统的命令,“hadoop dfs”属于早期版本的格式,已经过时,一般使用“hdfs dfs”。
“hadoop fs”也是文件系统操作命令,但使用范围更广,能够操作其他格式文件系统,如local、HDFS等,可以在本地与Hadoop分布式文件系统的交互操作中使用。
2. hdfs fsck [GENERIC_OPTIONS]<path> [-list-corruptfileblocks | [-move | -delete| -openforwrite][-files [-blocks [-locations | -racks]]]][-includeSnapshots]
fsck是一个文件系统健康状况检查工具,用来检查各类问题,比如,文件块丢失等(如图3-2所示)。但是,注意它不会主动恢复备份缺失的block,这个是由NameNode单独的线程异步处理的。
图3-2 fsck命令的运行结果
fsck命令的参数说明见表3-1。
表3-1 fsck参数的说明
3. hdfs datanode [-regular | -rollback | -rollingupgrade rollback]
运行一个HDFS集群的数据节点。参数说明见表3-2。
表3-2 hdfs datanode命令参数的说明
4. hdfs namenode [GENERIC_OPTIONS]
“hdfs namenode”是运行NameNode的命令,是一个比较核心的工具。该命令的主要参数说明见表3-3。
表3-3 hdfs namenode命令参数的说明
5. hdfs dfsadmin [GENERIC_OPTIONS]
dfsadmin是一个多任务的工具,我们可以使用它来获取HDFS的状态信息,以及在HDFS上执行的一系列管理操作。该命令的主要参数说明见表3-4。
表3-4 hdfs dfsadmin命令参数的说明
续表
dfsadmin命令的使用示例如图3-3所示。
图3-3 dfsadmin命令的使用示例
6. hdfs cacheadmin
管理员和用户通过“hdfs cacheadmin”命令管理缓存资源。
缓存指令由一个唯一的无重复的64位整数ID来标识。即使缓存指令后来被删除了,ID也不会重复使用。缓存池由一个唯一的字符串名称来标识。
(1)增加缓存:addDirective。
用法:hdfs cacheadmin -addDirective -path <path> -pool <pool-name> [-force][-replication<replication>][-ttl <time-to-live>]
参数说明见表3-5。
表3-5 addDirective的参数说明
(2)删除一个缓存:removeDirective。
用法:hdfs cacheadmin -removeDirective <id>
参数id指定要删除的缓存指令的ID。删除时,必须对该指令的缓存池拥有写权限。
(3)删除指定路径下的每一个缓存:removeDirectives。
用法:hdfs cacheadmin -removeDirectives <path>
参数path中设置要删除的缓存指令的路径。删除时必须对该指令的缓存池拥有写权限。
(4)缓存列表:listDirectives。
用法:hdfs cacheadmin -listDirectives [-stats][-path <path>][-pool <pool>]
参数说明见表3-6。
表3-6 listDirectives的参数说明
(5)新增缓存池:addPool。
用法:hdfs cacheadmin -addPool <name> [-owner <owner>][-group <group>][-mode<mode>][-limit <limit>][-maxTtl <maxTtl>
参数说明见表3-7。
表3-7 addPool的参数说明
(6)修改缓存池:modifyPool。
用法:hdfs cacheadmin -modifyPool <name> [-owner <owner>][-group <group>][-mode<mode>][-limit <limit>][-maxTtl <maxTtl>]
参数说明见表3-8。
表3-8 modifyPool的参数说明
(7)删除缓存池:removePool。
用法:hdfs cacheadmin -removePool <name>
参数name指定要删除的缓存池的名称。
(8)缓存池列表:listPools。
用法:hdfs cacheadmin -listPools [-stats][<name>]
参数说明见表3-9。
表3-9 addPool的参数说明
7. hdfs balancer [-threshold <threshold>][-policy <policy>]
HDFS集群非常容易出现机器与机器之间磁盘利用率不平衡的情况,尤其是增加新的数据节点时。保证HDFS中的数据平衡非常重要。HDFS出现不平衡的状况将引发很多问题,比如MapReduce程序无法很好地利用本地计算的优势、机器之间无法达到更好的网络带宽使用率等。
在Hadoop中,包含一个Balancer程序,可以调节HDFS集群平衡的状态。启动Balancer服务时,界面如图3-4所示。
图3-4 启动Balancer服务
服务启动后,集群管理人员可用balancer命令进行分析和再平衡数据,如图3-5所示。
图3-5 可用balancer命令进行分析和再平衡数据
参数threshold是判断集群是否平衡的目标参数,表示HDFS达到平衡状态的磁盘使用率偏差值。默认设置为10,参数取值范围是0~100。如果机器之间磁盘使用率偏差小于10%,我们就认为HDFS集群已经达到了平衡的状态。
8. hdfs version
hdfs version命令用于查看当前系统的版本,运行示例如图3-6所示。
图3-6 使用hdfs version命令查看当前系统的版本
3.1.2 Java接口操作
由于Hadoop本身就是使用Java语言编写的,理论上,通过Java API能够调用所有的Hadoop文件系统的操作接口。
Hadoop有一个抽象的文件系统概念,在Java抽象类org.apache.hadoop.fs中定义了接口。只要某个文件系统实现了这个接口,那么,它就可以作为Hadoop支持的文件系统。目前Hadoop能够支持的文件系统如表3-10所示。
表3-10 Hadoop文件类的实现
在Hadoop中,主要是定义了一组分布式文件系统和通用的I/O组件和接口,Hadoop的文件系统准确地应该称作Hadoop I/O。而HDFS是实现该文件接口的Hadoop自带的分布式文件项目,是对Hadoop I/O接口的实现。在处理大数据集时,为实现最优性能,通常使用HDFS存储。
org.apache.hadoop.fs包由接口类(FsConstants、Syncable等)、Java类(AbstractFileSystem、BlockLocation、FileSystem、FileUtil、FSDataInputStream等)、枚举类型(如CreateFlag)、异常类(ChecksumException、InvalidPathException等)和错误类(如FSError)组成。每个子对象中都定义了相应的方法,通过对org.apache.hadoop.fs包的封装与调用,可以拓展HDFS应用,更好地帮助用户使用集群海量存储。
在介绍Java接口操作之前,先介绍几个常用的Java类。
(1)FileSystem。
org.apache.hadoop.fs.FileSystem,通用文件系统基类,用于与HDFS文件系统交互,编写的HDFS程序都需要重写FileSystem类。通过FileSystem,可以非常方便地像操作本地文件系统一样操作HDFS集群文件。
FileSystem提供了get方法,一个是通过配置文件获取与HDFS的连接;一个是通过URL指定配置文件,获取与HDFS的连接,URL的格式为hdfs://namenode/xxx.xml。
方法的原型如下:
public static FileSystem get(Configuration conf) throws IOException; public static FileSystem get(URI uri, Configuration conf) throws IOException; public static FileSystem get(final URI uri, final Configuration conf, final String user) throws IOException, InterruptedException;
其中,Configuration(org.apache.hadoop.conf.Configuration)类对象封装了客户端或服务器的配置;URI是指文件在HDFS里存放的路径。
(2)FSDataInputStream。
org.apache.hadoop.fs.FSDataInputStream,文件输入流,用于读取HDFS文件,它是Java中DataInputStream的派生类,支持从任意位置读取流式数据。
常用的读取方法是从指定的位置,读取指定大小的数据至缓存区。方法如下所示:
int read(long position, byte[]buffer, int offset, int length)
还有用于随时定位的方法,可以定位到指定的读取点,如下所示:
void seek(long desired)
通过long getPos()方法,还可以获取当前的读取点。
(3)FSDataOutputStream。
org.apache.hadoop.fs.FSDataOutputStream,文件输出流,是DataOutputStream的派生类,通过这个类,能够向HDFS顺序写入数据流。
通常的写入方法为write,如下所示:
public void write(int b)
获取当前写入点的函数为long getPos()。
(4)Path。
org.apache.hadoop.fs.Path,文件与目录定位类,用于定义HDFS集群中指定的目录与文件绝对或相对路径。
可以通过多种方式构造Path,如通过URL的模式,通常编写方式为:
hdfs://ip:port/directory/filename
Path可以与FileSystem的open函数相关联,通过Path构造访问路径,用FileSystem进行访问。
(5)FileStatus。
org.apache.hadoop.fs.FileStatus,文件状态显示类,可以获取文件与目录的元数据、长度、块大小、所属用户、编辑时间等信息,同时,可以设置文件用户、权限等内容。
FileStatus有很多get与set方法,如获取文件长度的long getLen()方法、设置文件权限的setPermission(FsPermission permission)方法等。
下面,我们开始Hadoop的Java操作之旅。
1.创建文件
FileSystem类里提供了很多API,用来创建文件,其中,最简单的一个是:
public FSDataOutputStream create(Path f) throws IOException;
它创建一个Path类代表的文件,并返回一个输出流。这个方法有多个重载方法,可以用来设置是否覆盖已有文件、该文件复制的份数、写入时的缓冲区大小、文件块大小(block)、权限等。默认情况下,如果Path中文件的父目录(或者更上一级目录)不存在,这些目录会被自动创建。
2.读取数据
通过调用FileSystem实例的open方法打开文件,得到一个输入流。下面是使用FileSystem类读取HDFS中文件内容的完整程序:
import java.net.URI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; public class FileSystemCat { public static void main(String[]args) throws Exception { 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); } finally { IOUtils.closeStream(in); } } }
此外,FSDataInputStream类同时也实现了PositionedReadable(org.apache.hadoop.fs. 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[]buffer) throws IOException;
结合第2章内容,下面我们结合程序实现深入剖析HDFS读文件时的数据流向过程。
(1)客户端通过调用FileSystem.open()方法打开一个文件,对于HDFS来讲,其实是调用DistributedFileSystem实例的open方法。
(2)DistributedFileSystem通过远程方法调用访问NameNode,获取该文件的前几个blocks所在的位置信息;针对每个block, NameNode都会返回有该block数据信息的所有DataNodes节点,比如配置的dfs.replication为3,就会每个block返回3个DataNodes节点信息,这些节点是按距离客户端的远近排序的,如果发起读文件的客户端就在包含该block的DataNode上,那么这个DataNode就排第一位(这种情况在做Map任务时常见),客户端就会从本机读取数据。
DistributedFileSystem的open方法返回一个FSDataInputStream, FSDataInputStream里包装着一个DFSInputStream, DFSInputStream真正管理DataNodes和NameNode的I/O。
(3)客户端调用FSDataInputStream.read()方法,FSDataInputStream里已经缓存了该文件前几个block所在的DataNode的地址,于是,从第一个block的第一个地址(也就是最近的DataNode)开始连接读取。
(4)反复调用read()方法,数据不断地从DataNode流向客户端。
(5)当一个block的数据读完时,DFSInputStream会关闭当前DataNode的连接,打开下一个block所在的最优DataNode的连接继续读取;这些对客户端是透明的,在客户端看来,就是在读一个连续的流。
(6)这样,一个block一个block地读下去,当需要使用更多block的存储信息时,DFSInputStream会再次调用NameNode,获取下一批block的存储位置信息,直到客户端停止读取,调用FSDataInputStream.close()方法,整个读取过程结束。
小提示
文件操作还可以使用Hadoop URL的方式,示例代码如下:
import java.io.InputStream; import java.net.URL; import org.apache.hadoop.fs.FsUrlStreamHandlerFactory; import org.apache.hadoop.io.IOUtils; public class URLCat { static { URL.setURLStreamHandlerFactory(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); } } }
在上面的程序中,先设置URLStreamHandlerFactory,然后通过URL打开一个流,读取流,就得到了文件的内容,通过IOUtils.copyBytes()把读到的内容写出到标准输出流里,也就是控制台上,从而实现了类似于Linux里的cat命令的功能。最后关闭输入流。
3.写入数据
与读操作类似,Hadoop对于写操作也提供了一个类:FSDataOutputStream,这个类重载了很多java.io.DataOutputStream的write方法,用于写入很多类型的数据,比如int、char、字节数组等。
HDFS写文件的示例代码如下:
FileSystem hdfs = FileSystem.get(new Configuration()); Path path = new Path("/testfile"); FSDataOutputStream dos = hdfs.create(path); byte[]readBuf = "Hello World".getBytes("UTF-8"); dos.write(readBuf, 0, readBuf.length); dos.close(); hdfs.close();
如果希望向已有文件追加内容,可以调用:
public FSDataOutputStream append(Path f) throws IOException;
如果文件不存在时,append方法也可以用来新建一个文件。
下面,我们结合以上的程序,深入剖析HDFS写文件时的数据流向过程。
(1)客户端调用DistributedFileSystem.create()方法创建一个文件。
(2)DistributedFileSystem向NameNode发起远程方法调用,创建一个文件,但是,NameNode没有把它关联到任何block上去;NameNode在这一步做了很多检查工作,保证该文件当前不存在,客户端有创建该文件的权限等。如果这些检查都通过了,NameNode创建一条新文件记录;否则,创建失败,客户端返回IOException。DistributedFileSystem返回一个FSDataOutputStream,像读文件时一样,这个FSDataOutputStream里包装着一个DFSOutputStream,由它来实际处理与DataNodes和NameNode的通信。
(3)客户端向DFSOutputStream里写数据,DFSOutputStream把数据分成包,丢进一个称为data queue的队列中。DataStreamer负责向NameNode申请新的block,新的block被分配在了一个或多个(默认为3个)节点上,这些节点就形成一个管道。
(4)DataStreamer把data queue里的包拿出来,通过管道输送给第1个节点,第1个节点再通过管道输送给第2个节点,第2个再输送给第3个。以此类推。
(5)DFSOutputStream同时还在内部维护一个通知队列,名叫ack queue,里面保存发过的数据包。一个包只有被所有管道上的DataNodes通知收到了,才会被移除。如果任意一个DataNode接收失败了,首先,管道关闭,然后把ack queue里的包都放回到data queue的头部,以便使失败节点的下游节点不会丢失这些数据。打开管道,把坏节点移除,数据会继续向其他好节点输送,直到管道上的节点都完成了。如果少复制了一个节点,向NameNode报告一下,说现在这个block没有达到设定的副本数,然后就返回成功了,后期,NameNode会组织一个异步的任务,把副本数恢复到设定值。然后,接下来的数据包和数据块正常写入。
如果多个DataNodes都失败了,会检测hdfs-site.xml里的dfs.replication.min参数,默认值是1,意思是只要有1个DataNode接收成功,就认为数据写入成功了。客户端就会收到写入成功的返回。后期,Hadoop会发起异步任务把副本数恢复到dfs.replication设置的值。
以上操作对客户端都是透明的,客户端不知道发生了这些事情,只知道写文件成功了。
(6)当客户端完成数据写入后,调用流的close()方法,这个操作把data queue里的所有剩余的包都发给管道。
(7)等所有包都收到了写成功的反馈后,客户端通知NameNode写文件完成了。因为DataStream写文件前就先向NameNode申请block的位置信息了,所以写文件完成时,NameNode已知道每个block的位置信息,它只需等最小的副本数写成功,就可以返回成功。
4.文件读写位置
读取文件时(FSDataInputStream),允许使用seek()方法在文件中定位。支持随机访问,理论上,可以从流的任何位置读取数据,但调用seek()方法的开销是相当巨大的,应该尽量少调用,尽可能地使程序做到顺序读。
由于HDFS只允许对一个打开的文件顺序写入,或向一个已有文件的尾部追加,不允许在任意位置写,FSDataOutputStream没有seek方法。但FSDataOutputStream类提供了一个getPos()方法,可以查询当前在往文件的哪个位置写的写入偏移量:
public long getPos() throws IOException;
5.重命名
通过FileSystem.rename()方法,可为指定的HDFS文件重命名:
protected void rename(Path src, Path dst, Options) throws IOException;
示例代码实现如下:
Configuration conf = new Configuration(); FileSystem hdfs = FileSystem.get(conf); Path frpath = new Path("/test"); //旧的文件名 Path topath = new Path("/testNew"); //新的文件名 boolean isRename = hdfs.rename(frpath, topath); String result = isRename? "成功" : "失败";
6.删除操作
通过FileSystem.delete()方法删除指定的HDFS文件或目录(永久删除):
public boolean delete(Path f, boolean recursive) throws IOException;
其中,f为需要删除文件的完整路径,recursive用来确定是否进行递归删除。如果f是一个文件或空目录,则不论recursive是何值,都删除。如果f是一个非空目录,则recursive为true时,目录下内容全部删除;如果recursive为false,不删除,并抛出IOException。
示例代码实现如下:
Path f = new Path(fileName); boolean isExists = hdfs.exists(f); if (isExists) { //if exists, delete boolean isDel = hdfs.delete(f, true); System.out.println(fileName + " delete? \t" + isDel); } else { System.out.println(fileName + " exist? \t" + isExists); }
7.文件夹操作
FileSystem中创建文件夹的方法如下:
public boolean mkdirs(Path f) throws IOException;
与java.io.File.mkdirs方法一样,创建目录的同时,默认地创建缺失的父目录。我们一般不需要创建目录,一般在创建文件时,默认地就把所需的目录都创建好了。
目录创建的示例代码实现如下:
Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(conf); Path srcPath = new Path(path); boolean isok = fs.mkdirs(srcPath); if(isok) { System.out.println("create dir ok! "); } else { System.out.println("create dir failure"); } fs.close();
使用FileSystem的listStatus()方法能够列出某个目录中的所有文件:
public FileStatus[]listStatus(Path f) throws IOException public FileStatus[]listStatus(Path f, PathFilter filter) throws IOException public FileStatus[]listStatus(Path[]files) throws IOException public FileStatus[]listStatus(Path[]files, PathFilter filter) throws IOException
这一组方法都接收Path参数,如果Path是一个文件,返回值是一个数组,数组里只有一个元素,是这个Path代表的文件的FileStatus对象;如果Path是一个目录,返回值数组是该目录下的所有文件和目录的FileStatus组成的数组,有可能是一个0长数组;如果参数是Path[],则返回值相当于多次调用单Path,然后把返回值整合到一个数组里;如果参数中包含PathFilter,则PathFilter会对返回的文件或目录进行过滤,返回满足条件的文件或目录,条件由开发者自行定义。
FileSystem的globStatus方法利用通配符来列出文件和目录:
public FileStatus[]globStatus(Path pathPattern) throws IOException; public FileStatus[]globStatus(Path pathPattern, PathFilter filter) throws IOException;
文件夹删除操作与文件删除类似。
其他关于文件夹的操作方法还有FileSystem.getWorkingDirectory(返回当前工作目录)、FileSystem.setWorkingDirectory(更改当前工作目录)等。
8.属性操作
FileSystem类中的getFileStatus()方法返回一个FileStatus实例,该FileStatus实例中,包含了该Path(文件或目录)的元数据信息:文件大小、block大小、复制的份数、最后修改时间、所有者、权限等。示例代码实现如下:
FileStatus status = fs.getFileStatus(path); System.out.println("path = " + status.getPath()); System.out.println("owner = " + status.getOwner()); System.out.println("block size = " + status.getBlockSize()); System.out.println("permission = " + status.getPermission()); System.out.println("replication = " + status.getReplication());
3.1.3 WebHDFS
Hadoop提供的Java Native API支持对文件或目录的操作,为开发者提供了极大的便利。为满足许多外部应用程序操作HDFS文件系统的需求,Hadoop提供了两种基于HTTP方式的接口:一是用于浏览文件系统的Web界面;另一个是WebHDFS REST API接口。
启动HDFS时,NameNode和DataNode各自启动了一个内置的Web服务器,显示了集群当前的基本状态和信息。默认配置下NameNode的首页地址是http://namenode-name:50070/。这个页面列出了集群里的所有DataNode和集群的基本状态。
这个Web界面也可以用来浏览整个文件系统。使用NameNode首页上的Browse the file system链接,输入需要查看的目录地址,即可看到,如图3-7所示。
图3-7 Web界面
WebHDFS基于HTTP,通过GET、PUT、POST和DELETE等操作,支持FileSystem/FileContext的全部API。具体操作类型见表3-11。
表3-11 WebHDFS的操作
在使用WebHDFS REST API接口前,要先对Hadoop进行配置和授权认证。编辑hdfs-site.xml文件,添加启用WebHDFS(dfs.webhdfs.enabled)、kerberos验证(dfs.web. authentication.kerberos.principal、dfs.web.authentication.kerberos.keytab)等属性配置。配置完成后,启动WebHDFS服务即可,如图3-8所示。
图3-8 启动WebHDFS服务
WebHDFS默认的HTTP服务端口是14000。需要说明的是,WebHDFS的FileSystem模式是“webhdfs://”, URI的格式如下:
webhdfs://<HOST>:<HTTP_PORT>/<PATH>
与之对应的HDFS URI格式如下:
hdfs://<HOST>:<RPC_PORT>/<PATH>
在REST API接口中,在path之前插入前缀“/webhdfs/v1”,操作语句被追加到最后,相应的HTTP URL格式如下:
http://<HOST>:<HTTP_PORT>/webhdfs/v1/<PATH>? op=...
下面我们以具体实例,来测试一下WebHDFS的功能。使用curl命令工具在HDFS根目录下创建一个名为“webdir”的目录,如图3-9所示。
图3-9 WebHDFS创建目录的运行结果
3.1.4 其他接口
HDFS支持的使用接口除了前面介绍过的Java等以外,还有C、Thrift、HttpFS、HFTP、NFS等。下面简单介绍几种。
1. C接口
HDFS基于Java编写,并没有提供原生的C语言访问接口,但HDFS提供了基于JNI(Java Native Interface)的C调用接口libhdfs,使C语言访问HDFS成为可能。
libhdfs接口的头文件和库文件已包含在Hadoop发行版本中,可以直接使用。它的头文件hdfs.h一般位于{HADOOP_HOME}/include目录中,而其库文件libhdfs.so通常则位于{HADOOP_HOME}/lib/native目录中。不同的版本,库文件所在位置稍有不同。
通过libhdfs访问HDFS文件系统与使用C语言API访问普通操作系统的文件系统类似。C++访问HDFS的方式也与C语言类似。接口主要如下。
(1)建立、关闭与HDFS连接:hdfsConnect()、hdfsConnectAsUser()、hdfsDisconnect()。
(2)打开、关闭HDFS文件:hdfsOpenFile()、hdfsCloseFile()。当用hdfsOpenFile()创建文件时,可以指定replication和blocksize参数。
(3)读HDFS文件:hdfsRead()、hdfsPread()。
(4)写HDFS文件:hdfsWrite()。HDFS不支持随机写,只能是从文件头顺序写入。
(5)查询HDFS文件信息:hdfsGetPathInfo()。
(6)查询数据块所在节点信息:hdfsGetHosts()。返回一个或多个数据块所在数据节点的信息,一个数据块可能存在于多个数据节点上。
libhdfs中的函数是通过JNI调用Java虚拟机的,在虚拟机中构造对应的HDFS的Java类,然后反射调用该类的功能函数,占用内存较多,不适合对虚拟要求较高的场景。
下面是一个简单的例子:
#include "hdfs.h" int main(int argc, char **argv) { hdfsFS fs = hdfsConnect("default", 0); const char *writePath = "/tmp/testfile.txt"; hdfsFile writeFile = hdfsOpenFile(fs, writePath, O_WRONLY|O_CREAT, 0, 0, 0); if(! writeFile) { fprintf(stderr, "Failed to open %s for writing! \n", writePath); exit(-1); } char *buffer = "Hello, World! "; tSize num_written_bytes = hdfsWrite(fs, writeFile, (void*)buffer, strlen(buffer)+1); if (hdfsFlush(fs, writeFile)) { fprintf(stderr, "Failed to 'flush' %s\n", writePath); exit(-1); } hdfsCloseFile(fs, writeFile); }
2. HFTP
HFTP是一个可以实现从远程HDFS集群读取Hadoop文件系统数据的接口。HFTP默认是打开的,数据读取通过HTTP协议,允许以浏览器的方式访问和下载所有文件。这种方式带来便利的同时,也存在一定的安全隐患。
HFTP是一个只读的文件系统,如果试图用它写或者修改文件系统的状态,将会抛出一个错误。如果使用多个不同版本的HDFS集群时,需要在集群之间移动数据,HFTP是非常有用的。HFTP在不同HDFS版本之间都是兼容的,通常与distcp结合使用实现并行复制。
HSFTP是HFTP的一个扩展,默认使用HTTPS在传输时加密数据。
3. HttpFS
HttpFS是Cloudera公司提供的一个Web应用,一般部署在内嵌的Web服务器中,但独立于Hadoop的NameNode。
HttpFS是提供REST HTTP接口的服务器,可以支持全部HDFS文件系统操作。通过WebHDFS REST API,可以对HDFS进行读写等访问操作。与WebHDFS的区别是,不需要客户端,就可以访问Hadoop集群的每一个节点。
通过HttpFS,可以访问放置在防火墙后面的Hadoop集群数据。HttpFS可以作为一个网关角色,是唯一可以穿过防火墙访问内部集群数据的系统。
HttpFS的内置安全特性支持Hadoop伪身份验证和HTTP SPNEGO Kerberos及其他插件式(pluggable)验证机制。它还提供了对Hadoop代理用户的支持。