赞
踩
HDFS有数据块(block)的概念,一个块默认大小是128MB,HDFS中的文件会被分为多个块,每个块都是一个独立的存储单元,需要注意的是:当一个文件的块小于数据块大小是,他不会占用整个块的空间(当一个1MB的文件存储在128MB的数据块中时,文件占用的是1MB的空间)。
当块空间设置的非常小时,那么系统的瓶颈就会转为寻址的时间开销;如果特别大,那么数据传输的时间会明显大于寻址的时间。因此块的大小应设置的非常科学,默认128MB正常是没有问题的。
数据块概念的意义
1、一个文件的大小可以大于分布式系统中任意一个磁盘的容量,因为文件可以切分成多个块存储,并且可以利用集群中任意一个磁盘进行存储。
2、使用数据块而非整个文件作为存储单元,大大简化了存储子系统的设计复杂度。可以简化存储管理:由于块的大小是确定的,因此计算单个磁盘能存储多少个块就非常容易;同时也简化了对元数据的处理逻辑,块只需要存储物理数据,对于元数据(权限、文件模式等信息)并不需要与块一同存储,这样做可以使用其他系统单独统一管理这些元数据信息。
3、块非常适合与数据备份进而提高数据的容错能力和可用性。
4、hdfs fsck / -files -blocks 可以查看文件系统中各个文件由哪些块组成。
HDFS集群有两类节点,一个namenode节点(管理节点)和多个datanode节点(工作节点)。namenode管理文件系统的命名空间,他维护着文件系统树以及整个树内所有的文件和目录,这些信息以命名空间镜像文件和编辑日志文件的形式永久保存在本地磁盘上。namenode也记录着每个文件的各个块所在的数据节点信息,但是它不会永久保存块的数据点节点信息,因为这些信息会在系统启动的时候重建。
datanode是系统的工作节点,他们根据需要存储并检索数据块(受客户端或namenode调度),并且定期向namenode发送他们所存储的块的列表。
namenode只有一个,一旦宕机整个文件系统将无法使用,因此提升namenode的容错性和高可用性非常重要。
对于容错性,有两种方法:一个备份namenode中存储的文件系统元数据信息,一边持久化的写入本地磁盘备份,一边写入一个远程挂载的网络文件系统(NFS);二是运行一个辅助namenode,定期的合并编辑日志与命名空间镜像,以防止内存中的编辑日志过大,但是定期合并无法应对不定时的系统宕机。这两种方法在系统宕机是均需要重建namenode才能保证文件系统正常使用,都不是最好的解决方法。
HA:Hadoop配置了一对活动-备用的namenode。当活动的namenode失效了,备用namenode会接管它的任务保证文件系统不至于不能使用。实现HA,需要在架构上作如下修改。
- namenode之间需要通过高可用共享存储实现编辑日志的共享,当备用namenode接管之后保证编辑日志的元数据信息是最新的,无损的。
- datanode需要同时向两个namenode发送数据块处理报告,因为数据块的映射信息是存在与namenode的内存中而非硬盘中。
联邦HDFS提供添加namenode节点的形式横向扩展namenode,其中每个namenode负责管理文件系统命名空间的一部分。例如第一个namenode管理/usr目录下的所有文件,第二个namenode管理/share目录下的所有文件。联邦HDFS可以与实现namenode的HA的主备策略一起使用。
以上只是HDFS的核心概念的简要说明,于我个人而言,很多概念还是仅仅停留在知道这一层面,比如namenode的命名空间镜像和编辑日志到底是什么关系、怎么工作,这些只能暂时保留疑问,带着这些疑问继续阅读下去,希望可以尽快形成系统话的知识体系。
// 创建存放数据的目录
hdfs dfs -mkdir /input
// 上传数据
hdfs dfs -put 1901 /input
hdfs dfs -put 1902 /input
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
这里以举例的形式列举常用的几个命令,其他的可以自己尝试
命令 | 解释 |
---|---|
hdfs dfs -mkdir /input | 创建一个目录 /input |
hdfs dfs -ls / | 查看根目录下的文件 |
hdfs dfs -put /usr/local/aa.txt /input | 将本地/usr/local/aa.txt上传到hdfs根目录 |
hdfs dfs -copyFromLocal /usr/local/aa.txt /input | 同上 |
hdfs dfs -rm -r /aa.txt | 删除hdfs根目录下的aa.txt |
hdfs dfs -rm -r /input/* | 删除input目录下所有文件 |
hdfs dfs -rmdir /input | 删除hdfs根目录的input目录,如果input不为空则会删除失败 |
hdfs dfs -get /aa.txt /usr/local/ | 下载文件 |
首先通过一个示例代码,实现从HDFS读取一个文件,然后写入到输出流(System.out直接打印到控制台)。
private static void testFileSystemReadfile() throws IOException {
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
FSDataInputStream in = fileSystem.open(new Path("/input/wordcount.txt"));
IOUtils.copyBytes(in, System.out, 4096, true);
}
打jar包,执行命令,得到以下结果:hadoop jar mright-hadoop-test-1.0-SNAPSHOT.jar com.mright.hadoop.hdfs.HDFSBlog
public static FileSystem get(Configuration conf) throws IOException {
return get(getDefaultUri(conf), conf);
}
public static FileSystem get(URI uri, Configuration conf) throws IOException {
String scheme = uri.getScheme();
String authority = uri.getAuthority();
if (scheme == null && authority == null) {
return get(conf);
} else {
if (scheme != null && authority == null) {
URI defaultUri = getDefaultUri(conf);
if (scheme.equals(defaultUri.getScheme()) && defaultUri.getAuthority() != null) {
return get(defaultUri, conf);
}
}
String disableCacheName = String.format("fs.%s.impl.disable.cache", scheme);
return conf.getBoolean(disableCacheName, false) ? createFileSystem(uri, conf) : CACHE.get(uri, conf);
}
}
public static FileSystem get(final URI uri, final Configuration conf, String user) throws IOException, InterruptedException {
String ticketCachePath = conf.get("hadoop.security.kerberos.ticket.cache.path");
UserGroupInformation ugi = UserGroupInformation.getBestUGI(ticketCachePath, user);
return (FileSystem)ugi.doAs(new PrivilegedExceptionAction<FileSystem>() {
public FileSystem run() throws IOException {
return FileSystem.get(uri, conf);
}
});
}
Configuration对象封装了客户端或者服务器的配置,通过设置设置配置文件读取类路径实现,在这里是:$HADOOP_HOME/etc/hadoop/core-site.xml。
fileSystem.open(new Path(“/input/wordcount.txt”));用来打开一个hdfs文件的输入流
IOUtils.copyBytes(in, System.out, 4096, true)
FileSystem创建文件,并写入数据。这些API没什么技巧可言,示例如下,会用就好。特别注意:创建文件夹和创建文件写入数据时,有个api是支持进度条的,它有频率的回调你定义的方法,直至执行完毕。
/**
* 创建文件夹,可以多级目录一起指定,同时创建
*/
private static void mkdirs() throws IOException {
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
fileSystem.mkdirs(new Path("/aaa/fff"));
}
/**
* 读取文件内容,入门程序已有介绍
*/
private static void read() throws IOException {
String inputUrl = "hdfs://localhost:9000/input/wordcount.txt";
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
InputStream in = null;
try {
in = fileSystem.open(new Path(inputUrl));
IOUtils.copyBytes(in, System.out, 4096, false);
} finally {
IOUtils.closeStream(in);
}
}
/**
* 在文件结尾追加内容
*/
private static void append() throws IOException {
// HDFS文件系统目录,读取的是core-site.xml中的URI配置
String outputUrl = "/input/wordcount.txt";
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
FSDataOutputStream out = fileSystem.append(new Path(outputUrl));
IOUtils.copyBytes(new ByteArrayInputStream("山有扶苏,隰有荷华。不见子都,乃见狂且。\r\n山有乔松,隰有游龙,不见子充,乃见狡童。\r\n".getBytes()), out, 4096, true);
}
/**
* 在文件结尾追加内容,显示进度条,兰姆达表达式中代码所示
*/
private static void appendProcessable() throws IOException {
String outputUrl = "/input/wordcount.txt";
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
FSDataOutputStream outputStream = fileSystem.append(new Path(outputUrl), 1024, () -> System.out.print("."));
IOUtils.copyBytes(new ByteArrayInputStream("这是显示进度条的追加内容方法\r\n".getBytes()), outputStream, 4096, true);
}
/**
* 创建文件,并将输入流的数据写入文件
*/
private static void create() throws IOException {
// 本地文件系统目录
String inputUrl = "/Users/mright/apps/document-temp/wordcount.txt";
// HDFS文件系统目录,读取的是core-site.xml中的URI配置
String outputUrl = "/input/wordcount.txt";
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
FSDataOutputStream out = fileSystem.create(new Path(outputUrl), true);
IOUtils.copyBytes(new FileInputStream(inputUrl), out, 4096, true);
}
/**
* 创建文件,并将输入流的数据写入文件,显示进度条,兰姆达表达式所示
*/
private static void createProgressable() throws IOException {
// 本地文件系统目录
String inputUrl = "/Users/mright/apps/document-temp/wordcount.txt";
// HDFS文件系统目录,读取的是core-site.xml中的URI配置
String outputUrl = "/input/wordcount.txt";
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
FSDataOutputStream out = fileSystem.create(new Path(outputUrl), true, 1024, () -> System.out.print("."));
IOUtils.copyBytes(new FileInputStream(inputUrl), out, 4096, true);
}
// 对于文件夹和文件,API是一摸一样的,只不过获取的元数据信息有区别
private static void testFile(String url) throws IOException {
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
FileStatus fileStatus = fileSystem.getFileStatus(new Path(url));
System.out.println("文件是否存在:" + fileSystem.exists(new Path(url)));
System.out.println("文件路径:" + fileStatus.getPath());
System.out.println("URI:" + fileStatus.getPath().toUri().getPath());
System.out.println("是否是一个目录:" + fileStatus.isDirectory());
System.out.println("文件内容的长度:" + fileStatus.getLen());
System.out.println("最后一次修改时间:" + new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:sss").format(new Date(fileStatus.getModificationTime())));
System.out.println("分片数:" + fileStatus.getReplication());
System.out.println("块大小:" + fileStatus.getBlockSize() / 1024 / 1024 + "MB");
System.out.println("文件所有者:" + fileStatus.getOwner());
System.out.println("文件所有者所在组:" + fileStatus.getGroup());
System.out.println("权限详情:" + fileStatus.getPermission());
}
public static void main(String[] args) throws IOException {
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
FileStatus[] fileStatuses = fileSystem.listStatus(new Path("/input"));
Path[] paths = FileUtil.stat2Paths(fileStatuses);
Arrays.stream(paths).forEach(System.out::println);
}
结果如下:
关于读取文件的读取和选择,权威指南还有两块知识点:通过通配符批量选择文件;通过PathFilter从批量选中的文件中过滤掉某几个不想要的文件。在实际的开发中,通过通配符批量选中文件确实可以省很多事,这只是两个小知识点,大家有兴趣可以自行百度下就好,我就不赘述。
删除数据
private static void deleteFile() throws IOException {
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
fileSystem.delete(new Path("/input/wordcount.txt"), true);
}
再次查看hdfs的文件目录,/input/wordcount.txt已经不存在
hdfs的基本操作本章大体已全部整理进来,入门和基本HDFS开发任务通过本文完全没有问题。但是对于文件读取和文件写入时的数据流分析的内容,本章没有多做说明,主要是我只是粗略阅读了这部分内容,不敢妄加整理;二来最近项目工期紧,数据流的知识在后期会补上。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。