注:该文内容部分来源于ChinaHadoop.cn上的hadoop视频教程。
一. HDFS概述
HDFS即Hadoop Distributed File System, 源于Google发表于2003年的论文,是一种分布式的文件系统。
HDFS优点:
- 高容错性(数据自动保存多个副本)
- 适合批处理
- 适合大数据处理
- 流式文件访问(一次性写入,多次读取)
- 建立在廉价机器上
HDFS缺点:
- 不善于处理低延迟数据访问
- 不善于处理小文件存取(元数据存放在namenode内存中,消耗大量内存)
- 不支持并发写入,文件随机修改(一个文件同时只能被一个写者修改,只支持append)
二. HDFS设计思想
以上分布式文件系统的问题在于由于文件的大小不一致,导致了各个服务器的存储空间不均衡,个别服务器相对于其他服务器可能占用率非常高。其次,由于文件是整个存储在一个节点上,在读取文件时,难以实现并行读取,使得某一节点成为系统的网络瓶颈。
HDFS的设计思路是将每个文件分成大小相同的block,以多副本的形式存放在不同节点上,同时维护一个数据块和节点的映射关系,这样的设计解决了第一种方案存在的负载不均衡以及网络瓶颈的问题。
Active NameNode |
StandBy NameNode |
DataNode |
Client |
主Master(只有一个) | 主Master的热备份 | Slaves(有多个) | 文件切分 |
管理HDFS名称空间 | 定期合并fsimage和fsedits,推送给主Master | 存储数据块 | 管理和访问HDFS |
管理数据块映射关系 | 当Active NameNode出现故障, 快速切换成新的Active NameNode |
执行数据读写 | |
配置副本策略 | |||
处理客户端读取请求 |
HDFS数据块
- 文件被切分成大小相同的数据块,例如64mb,如果文件大小不足64mb的也单独存成一个block。数据块大小可以配置。
- 数据块如此之大的好处是时间大多花在了数据存取上,而不是寻道上,提高了吞吐率。
- 每个数据块都有多个副本,个数可以配置。
HDFS写流程的特点是流水线式的写入,DataNode之间互相的写入数据,这样的好处是分摊了网络瓶颈。
HDFS块副本放置策略
- 副本1:同client的节点上
- 副本2:不同机架的节点上
- 副本3:与第二个副本同一机架的不同节点上
- 其他副本:随机挑选
HDFS可靠性策略对于3种常见错误的处理
- 文件损坏:利用CRC32校验,使用其他副本替代。
- 网络或者机器失效:DataNode定期向NameNode发送心跳。
- NameNode失效:主备NameNode切换。
三. 使用Java API访问HDFS
HDFS Java API介绍
- Configuration类:该类的对象封装了配置信息,这些配置信息来自core-*.xml;
- FileSystem类:文件系统类,可使用该类的方法对文件/目录进行操作。一般通过FileSystem的静态方法get获得一个文件系统对象;
- FSDataInputStream和FSDataOutputStream类:HDFS中的输入输出流。分别通过FileSystem的open方法和create方法获得。
操作文件程序的基本框架
- 得到Configuration对象
- 得到FileSystem对象
- 进行文件操作
代码实例
将本地文件上传到HDFS
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*; public class CopyFromLocal {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9001");
FileSystem fs = FileSystem.get(conf);
Path src = new Path("/usr/local/hadoop-2.7.2/README.txt");
Path dst = new Path("hdfs://localhost:9001/home/");
fs.copyFromLocalFile(src, dst);
System.out.println("upload to "+conf.get("fs.default.name"));
fs.close();
}
}
将HDFS上的文件传回到本地
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;public class CopyToLocal {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9001");
FileSystem fs = FileSystem.get(conf);
Path src = new Path("hdfs://localhost:9001/home/README.txt");
Path dst = new Path("/home/hadoop/Desktop/");
fs.copyToLocalFile(src, dst);
fs.close();
}
}
在HDFS上创建文件
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*; public class CreateFile { public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9001");
FileSystem fs = FileSystem.get(conf);
Path dfs = new Path("hdfs://localhost:9001/home/test.txt");
FSDataOutputStream outputStream = fs.create(dfs);
byte[] buff = "hello world!".getBytes();
outputStream.write(buff,0, buff.length);
outputStream.close();
fs.close();
}
}
删除文件/目录, 当删除对象为目录时,将第二个参数设为true否则将产生异常
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*; public class DeleteFile {
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9001");
FileSystem fs = FileSystem.get(conf);
Path dfs = new Path("hdfs://localhost:9001/home/test.txt");
System.out.println(fs.delete(dfs,true));
fs.close();
}
}
创建目录
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*; public class MakeDir { public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9001");
FileSystem fs = FileSystem.get(conf);
Path dfs = new Path("hdfs://localhost:9001/home1/");
System.out.println(fs.mkdirs(dfs));
fs.close();
}
}
读取文件
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*; public class ReadFile {
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9001");
FileSystem fs = FileSystem.get(conf);
Path dst = new Path("hdfs://localhost:9001/home/test.txt");
if(fs.exists(dst) && !fs.isDirectory(dst)) {
FSDataInputStream is = fs.open(dst);
FileStatus stat = fs.getFileStatus(dst);
byte[] buffer = new byte[(int) stat.getLen()];
is.read(buffer);
System.out.println(new String(buffer));
is.close();
fs.close();
} else {
throw new Exception("fail to read file "+dst.toString());
}
}
}
追加文件内容,注:需要将hdfs-site.xml中的dfs.support.append属性设置为true
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*; public class AppendFile { public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
conf.addResource(new Path("/usr/local/hadoop-2.7.2/etc/hadoop/core-site.xml"));
FileSystem fs = FileSystem.get(conf);
Path dfs = new Path("hdfs://localhost:9001/home/test.txt");
FSDataOutputStream outputStream = fs.append(dfs);
byte[] buff = "test".getBytes();
outputStream.write(buff);
outputStream.close();
fs.close();
}
}