Rethink Hadoop 3.0 之三:对HDFS的操作
HDFS里文件操作的命令都是通过 hdfs dfs 和对文件操作的命令以及参数构成的。比如我们在Linux 里常用的 ls命令,在hdfs里就是 hdfs dfs -ls。因为hdfs还有很多对hdfs系统进行维护的命令,比如进行数据平衡命令(hdfs balancer ),images/edits 文件检查的命令,所以为了把文件操作命令独立开来,所以必须以hdfs dfs 来操作hdfs文件。
命令行操作
1.将本地文件复制到HDFS
put命令
hdfs dfs -put localfile /hadoopfile
2.将HDFS文件复制到本地
get命令
hdfs dfs -get /hadoopfile localfile
3.创建一个文件夹
mkdir命令
hdfs dfs -mkdir [-p] /hadooppath/dir
-p 参数会在父目录不存在的情况下创建父目录。
4.删除一个文件/文件夹
rm命令
hdfs dfs -rm [-f][-r] /hadooppath/dir
-f 会强制删除,不会出现警告提示。
-r 删除文件时会删除文件夹下所有内容。
5.修改文件的备份数
-setrep命令
hdfs dfs -setrep [-w] <备份数> <hadoop路径>
-w 表示等待命令执行完成。这个过程可能比较久
如果路径是个文件夹,则这个文件夹下的所有文件的备份数都会被改变。
可以看到对HDFS文件的操作和linux下对文件操作命令非常相似。这让我们很容易上手。更多命令可以参考Hadoop官方文档
Java对HDFS的操作
我们主要通过org.apache.hadoop.fs.FileSystem类来对HDFS进行访问。我们写的程序最好是编译成一个jar放到hadoop集群内部去执行,它的好处是:
1. 不需要进行Hadoop依赖的jar文件的设置。
2. 数据文件传输更高效。因为在同一个网络内部。
如果你需要在Hadoop集群外部通过Java对HDFS进行访问。
编译配置
程序编译你需要设置的Maven依赖是:
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.0.0</version>
</dependency>
运行配置
一个方便的办法是去HDFS集群的一个节点运行下边命令查看HDFS运行依赖的jar文件。
hdfs classpath
将这些jar文件都拷贝到需要运行HDFS访问的机器上。并将这些jar都放到classpath里。如果你的程序是运行在Hadoop的节点上。你只要通过下边的命令来执行就可以:
hadoop jar yourapp.jar mainclassname [args...]
Java 程序示例
package fun.rethink;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class HadoopDemo {
FileSystem fs;
public HadoopDemo(String hadoopPath) {
System.setProperty("HADOOP_USER_NAME", "root");
Configuration conf = new Configuration();
conf.set("fs.defaultFS", hadoopPath);
try {
fs = FileSystem.get(conf);
} catch (IOException e) {
e.printStackTrace();
}
}
public void testWrite(String filePath) {
Path p = new Path(filePath);
FSDataOutputStream fsos=null;
try {
fsos = fs.create(p);
for(int i =1;i<=1000;i++) {
fsos.write((i+"\n").getBytes("UTF-8"));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
if (fsos!=null) {
try {
fsos.hsync();
fsos.close();
}
catch(Exception ex) {
ex.printStackTrace();
}
}
}
}
public void testRead(String filePath) {
Path p = new Path(filePath);
FSDataInputStream fsis=null;
try {
fsis = fs.open(p);
InputStreamReader isr = new InputStreamReader(fsis);
BufferedReader br = new BufferedReader(isr);
String line;
while((line = br.readLine())!=null) {
System.out.println(line);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
if(fsis!=null) {
try {
fsis.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
HadoopDemo hd =new HadoopDemo("hdfs://hadoop1:9000");
hd.testWrite("/test/number");
hd.testRead("/test/number");
}
}
HDFS文件读写解析
文件读取
1.应用程序获得一个FileSystem的实例,并且通过Open方法去打开一个HDFS文件。
2.FileSystem通过我们的配置找到HDFS的NameNode,获取到我们要读取文件的开始的块,以及这些块所在的DataNode的地址列表。
3.应用程序通过FSDataInputStream去读取数据。
4.FSDataInputStream会根据DataNode地址去按顺序去获取DataNode里的数据块。
5.如果一个数据块读取完毕,会从NameNode获取文件下一个数据块做在的DataNode的地址,并且读取数据,这个对于应用程序是透明的。
6.应用程序关闭FSDataInputStream。
需要注意的是用户的程序是直接从DataNode获取数据,而不需要通过NameNode。
文件写入
1.应用程序获得一个FileSystem的实例,并且通过Create方法去创建一个HDFS文件。
2.FileSystem通过我们的配置找到HDFS的NameNode,申请创建一个文件。
3.应用程序通过FSDataOutputStream去写数据。
4.FSDataOutputStream会向NameNode请求一组DataNode,然后开始流式的写入过程。一个个小的数据包先写入DataNode1,接着流入DataNode2,然后到DataNode3。
5.DataNode3写入数据后返回,DataNode2返回,DataNode1返回,这时一个数据包才算写完。由于大部分时间3个dataNode都是在并行的写入数据,所以写3个Node和写1个Node时间区别不大。这也是流式写入的优势。
6.应用程序关闭FSDataOutputStream。
7.通知NameNode文件写入完成。