Hadoop分布式文件系统(HDFS)是hadoop上部署的存储架构。有着高容错性(fault-tolerant)的特点,并且设计用来部署在低廉的(low-cost)硬件上。而且它提供高吞吐量(high throughput)来访问应用程序的数据,适合那些有着超大数据集(large data set)的应用程序。为了熟练应用hadoop,必须对HDFS文件进行创建和读写等操作。
本关任务
利用HDFS文件系统开放的API对HDFS系统进行文件的创建和读写
要求:
在HDFS的路径/user/hadoop/下新建文件myfile,并且写入内容“china cstor cstor cstor china”;
输出HDFS系统中刚写入的文件myfile的内容
相关知识
HDFS文件系统
HDFS设计成能可靠地在集群中大量机器之间存储大量的文件,它以块序列的形式存储文件。文件中除了最后一个块,其他块都有相同的大小(一般64M)。属于文件的块为了故障容错而被复制到不同节点备份(备份数量有复制因子决定)。块的大小和读写是以文件为单位进行配置的。
HDFS中的文件是一次写的,并且任何时候都只有一个写操作,但是可以允许多次读。
创建HDFS文件
-
客户端通过在DistributedFileSystem中调用create()来创建文件。
-
DistributedFileSystem 使用RPC去调用namenode,在文件系统的命名空间创一个新的文件,没有块与之相联系。namenode执行各种不同的检查以确保这个文件不会已经存在,并且在client有可以创建文件的适当的许可。如果检查通过,namenode就会生成一个新的文件记录;否则,文件创建失败并向client抛出一个IOException异常。分布式文件系统返回一个文件系统数据输出流,让client开始写入数据。就像读取事件一样,文件系统数据输出流控制一个DFSOutputStream,负责处理datanode和namenode之间的通信。
-
在client写入数据时,DFSOutputStream将它分成一个个的包,写入内部队列,称为数据队列。数据流处理数据队列,数据流的责任是根据适合的datanode的列表要求namenode分配适合的新块来存储数据副本。这一组datanode列表形成一个管线————假设副本数是3,所以有3个节点在管线中。
-
数据流将包分流给管线中第一个的datanode,这个节点会存储包并且发送给管线中的第二个datanode。同样地,第二个datanode存储包并且传给管线中的第三个数据节点。
-
DFSOutputStream也有一个内部的数据包队列来等待datanode收到确认,称为确认队列。一个包只有在被管线中所有的节点确认后才会被移除出确认队列。如果在有数据写入期间,datanode发生故障, 则会执行下面的操作,当然这对写入数据的client而言是透明的。首先管线被关闭,确认队列中的任何包都会被添加回数据队列的前面,以确保故障节点下游的datanode不会漏掉任意一个包。为存储在另一正常datanode的当前数据块制定一个新的标识,并将该标识传给namenode,以便故障节点datanode在恢复后可以删除存储的部分数据块。从管线中删除故障数据节点并且把余下的数据块写入管线中的两个正常的datanode。namenode注意到块复本量不足时,会在另一个节点上创建一个新的复本。后续的数据块继续正常接收处理。只要dfs.replication.min的副本(默认是1)被写入,写操作就是成功的,并且这个块会在集群中被异步复制,直到其满足目标副本数(dfs.replication 默认值为3)。
-
client完成数据的写入后,就会在流中调用close()。
-
在向namenode节点发送完消息之前,此方法会将余下的所有包放入datanode管线并等待确认。namenode节点已经知道文件由哪些块组成(通过Data streamer 询问块分配),所以它只需在返回成功前等待块进行最小量的复制。
读取HDFS文件
- 客户端通过调用FileSystem对象的open()来读取希望打开的文件。对于HDFS来说,这个对象是分布式文件系统的一个实例。
- DistributedFileSystem通过RPC来调用namenode,以确定文件的开头部分的块位置。对于每一块,namenode返回具有该块副本的datanode地址。此外,这些datanode根据他们与client的距离来排序(根据网络集群的拓扑)。如果该client本身就是一个datanode,便从本地datanode中读取。DistributedFileSystem 返回一个FSDataInputStream对象给client读取数据,FSDataInputStream转而包装了一个DFSInputStream对象。
- 接着client对这个输入流调用read()。存储着文件开头部分块的数据节点地址的DFSInputStream随即与这些块最近的datanode相连接。
- 通过在数据流中反复调用read(),数据会从datanode返回client。
- 到达块的末端时,DFSInputStream会关闭与datanode间的联系,然后为下一个块找到最佳的datanode。client端只需要读取一个连续的流,这些对于client来说都是透明的。
- 在读取的时候,如果client与datanode通信时遇到一个错误,那么它就会去尝试对这个块来说下一个最近的块。它也会记住那个故障节点的datanode,以保证不会再对之后的块进行徒劳无益的尝试。client也会确认datanode发来的数据的校验和。如果发现一个损坏的块,它就会在client试图从别的datanode中读取一个块的副本之前报告给namenode。
- 这个设计的一个重点是,client直接联系datanode去检索数据,并被namenode指引到块中最好的datanode。因为数据流在此集群中是在所有datanode分散进行的。所以这种设计能使HDFS可扩展到最大的并发client数量。同时,namenode只不过提供块的位置请求(存储在内存中,十分高效),不是提供数据。否则如果客户端数量增长,namenode就会快速成为一个“瓶颈”。
HDFS文件流操作
HDFS文件还提供文件数据流操作API,利用这些可以将文件读取简化为三大步骤。
获取文件系统实例化创建文件
通过获取数据流进行写入,完成后关闭数据流
通过输出数据流将文件内容输出
1.获取文件系统
//读取hadoop文件系统配置
Configuration conf = new Configuration(); //实例化设置文件,configuration类实现hadoop各模块之间值的传递
FileSystem fs = FileSystem.get(conf); //是hadoop访问系统的抽象类,获取文件系统, FileSystem的get()方法得到实例fs,然后fs调动create()创建文件,open()打开文件
System.out.println(fs.getUri());
Path file = new Path(""); //命名一个文件及路径
if (fs.exists(file)) {
System.out.println("File exists.");
} else
{
2.通过输入数据流进行写入
FSDataOutputStream outStream = fs.create(file); //获取文件流
outStream.writeUTF("XXXXXXXX"); //使用文件流写入文件内容
3.通过输出数据流将文件内容输出
// FSDataInputStream实现了和接口,从而使Hadoop中的文件输入流具有流式搜索和流式定位读取的功能
String data = inStream.readUTF(); //使用输出流读取文件
编程要求
本关的编程任务是补全右侧代码片段中的代码,具体要求及说明如下:
在主函数main中已获取hadoop的系统设置,并在其中创建HDFS文件。在main函数中,指定创建文档路径(必须设置为/user/hadoop/myfile才能评测),输入内容必须是本关要求内容才能评测。
添加读取文件输出部分
本关只要求在指定区域进行代码编写,其他区域仅供参考请勿改动。
测试说明
本关无测试样例,直接比较文件内容确定输出是否为“china cstor cstor cstor china”
注:由于启动服务、编译等耗时,以及hdfs文件操作过程资源消耗较大且时间较长,因而单个用户使用资源有限,评测时间较长(30s左右)!
请耐心等待!相信自己!通往成功的路上不会太久!
本关的许多概念和操作比较抽象,但是机智的你一定会在接下来的学习中不断深入了解其含义并最终熟练操作的!
开始你的任务吧,祝你成功!
如果你觉得这一关的内容对你有帮助,请你在下面点赞。如果你对本关有任何意见和建议,欢迎在下面吐槽!
代码文件
//test2
import java.io.IOException;
import java.sql.Date;
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;
public class hdfs {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
System.out.println(fs.getUri());
Path file = new Path("/user/hadoop/myfile");
if (fs.exists(file)) {
System.out.println("File exists.");
} else
{
FSDataOutputStream outStream = fs.create(file);
outStream.writeUTF("china cstor cstor cstor china");
outStream.close();
}
FSDataInputStream inStream = fs.open(file);
String data = inStream.readUTF();
FileSystem hdfs = file.getFileSystem(conf);
FileStatus[] fileStatus = hdfs.listStatus(file);
for(FileStatus status:fileStatus)
{
System.out.println("FileOwer:"+status.getOwner());
System.out.println("FileReplication:"+status.getReplication());
System.out.println("FileModificationTime:"+new Date(status.getModificationTime()));
System.out.println("FileBlockSize:"+status.getBlockSize());
}
System.out.println(data);
System.out.println("Filename:"+file.getName());
inStream.close();
fs.close();
}
}
注释
import java.io.IOException;
import java.sql.Date;
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;
public class hdfs {
public static void main(String[] args) throws IOException {
//throws IOException捕获异常声明
//****请根据提示补全文件创建过程****//
/*********begin*********/
//实现文件读写主要包含以下步骤:
//读取hadoop文件系统配置
//实例化设置文件,configuration类实现hadoop各模块之间值的传递
//FileSystem是hadoop访问系统的抽象类,获取文件系统, FileSystem的get()方法得到实例fs,然后fs调动create()创建文件,调用open()打开文件,调用close()关闭文件
//*****请按照题目填写要创建的路径,其他路径及文件名无法被识别******//
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
System.out.println(fs.getUri());
// Path file = new Path("/user/hadoop/myfile");
Path file = new Path("/user/hadoop/myfile");
/*********end**********/
if (fs.exists(file)) {
System.out.println("File exists.");
} else
{
//****请补全使用文件流将字符写入文件过程,使用outStream.writeUTF()函数****//
/*********begin*********/
FSDataOutputStream outStream = fs.create(file);
outStream.writeUTF("china cstor cstor cstor china");
outStream.close();
/*********end**********/
}
//****请补全读取文件内容****//
/*********begin*********/
// 提示:FSDataInputStream实现接口,使Hadoop中的文件输入流具有流式搜索和流式定位读取的功能
FSDataInputStream inStream = fs.open(file);
String data = inStream.readUTF();
/*********end**********/
//输出文件状态
//FileStatus对象封装了文件的和目录的元数据,包括文件长度、块大小、权限等信息
FileSystem hdfs = file.getFileSystem(conf);
FileStatus[] fileStatus = hdfs.listStatus(file);
for(FileStatus status:fileStatus)
{
System.out.println("FileOwer:"+status.getOwner());//所有者
System.out.println("FileReplication:"+status.getReplication());//备份数
System.out.println("FileModificationTime:"+new Date(status.getModificationTime()));//目录修改时间
System.out.println("FileBlockSize:"+status.getBlockSize());//块大小
}
System.out.println(data);
System.out.println("Filename:"+file.getName());
inStream.close();
fs.close();
}
}