跟我一起读《Hadoop权威指南》 第三篇 -- HDFS (Hadoop分布式文件系统)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/maniacer/article/details/80149447

HDFS概念

  • HDFS(Hadoop Distributed FileSystem)是Hadoop旗舰级别文件系统,用来存储超大文件(从几百MB到几百TB级别数据)、流式数据访问、高延时的以及一次写入多次读取的文件系统。

数据块

  • HDFS有数据块(block)的概念,一个块默认大小是128MB,HDFS中的文件会被分为多个块,每个块都是一个独立的存储单元,需要注意的是:当一个文件的块小于数据块大小是,他不会占用整个块的空间(当一个1MB的文件存储在128MB的数据块中时,文件占用的是1MB的空间)。

  • 当块空间设置的非常小时,那么系统的瓶颈就会转为寻址的时间开销;如果特别大,那么数据传输的时间会明显大于寻址的时间。因此块的大小应设置的非常科学,默认128MB正常是没有问题的。

  • 数据块概念的意义

1、一个文件的大小可以大于分布式系统中任意一个磁盘的容量,因为文件可以切分成多个块存储,并且可以利用集群中任意一个磁盘进行存储。
2、使用数据块而非整个文件作为存储单元,大大简化了存储子系统的设计复杂度。可以简化存储管理:由于块的大小是确定的,因此计算单个磁盘能存储多少个块就非常容易;同时也简化了对元数据的处理逻辑,块只需要存储物理数据,对于元数据(权限、文件模式等信息)并不需要与块一同存储,这样做可以使用其他系统单独统一管理这些元数据信息。
3、块非常适合与数据备份进而提高数据的容错能力和可用性。
4、hdfs fsck / -files -blocks 可以查看文件系统中各个文件由哪些块组成。

namenode和datanode

  • 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的内存中而非硬盘中。

块缓存

  • 通常datanode从磁盘中读取块,当对于访问频繁的文件,其对应的块会被显式的缓存在datanode的内存中,以堆外块缓存的形式存在。在执行一些作业的时候,可以利用块缓存提高读操作的效率。

联邦HDFS

  • 联邦HDFS提供添加namenode节点的形式横向扩展namenode,其中每个namenode负责管理文件系统命名空间的一部分。例如第一个namenode管理/usr目录下的所有文件,第二个namenode管理/share目录下的所有文件。联邦HDFS可以与实现namenode的HA的主备策略一起使用。

  • 以上只是HDFS的核心概念的简要说明,于我个人而言,很多概念还是仅仅停留在知道这一层面,比如namenode的命名空间镜像和编辑日志到底是什么关系、怎么工作,这些只能暂时保留疑问,带着这些疑问继续阅读下去,希望可以尽快形成系统话的知识体系。

命令行操作

  • 在第二篇的气象统计的作业中,我们在运行job前的数据准备工作,已经使用过了几个命令行操作,如下:
// 创建存放数据的目录
hdfs dfs -mkdir /input

// 上传数据
hdfs dfs -put 1901 /input
hdfs dfs -put 1902 /input
  • 这里的/input,其实全拼应该是:hdfs://localhost:9000/input,这是HDFS文件系统的目录方式。因为我们在第一篇的安装过程中,已经设置了core-site.xml的fs.defaultFS,因此可以直接写/input。在很多博客或者书中都用hadoop dfs -xxx,在Hadoop2之后已经不推荐这样用,采用hdfs dfs -xxx。
<property>
        <name>fs.defaultFS</name>
        <value>hdfs://localhost:9000</value>
</property>
  • 可以通过 hdfs dfs -help 命令查看命令行的所有操作命令,下边截取了一部分命令,这里有:copyFromLocal、copyToLocal、count、cp等等命令,大部分跟Linux系统的文件和文件夹的操作一致,只不过每个Linux的命令前加上:hdfs dfs -copyFromLocal …
  • 这里写图片描述

  • 这里以举例的形式列举常用的几个命令,其他的可以自己尝试

命令 解释
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/ 下载文件

Java API

通过FileSystem API读取数据

首先通过一个示例代码,实现从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
这里写图片描述

  • FileSystem是一个通用的文件系统API,所以第一步我们是确定我们需要使用的文件系统。获取FileSystem实例有以下三个静态工厂方法
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。

    • 第一个方法返回的是默认文件系统,在core-site.xml中配置;
    • 第二个方法通过给定的URI方案和权限来确定使用的文件系统,如果URI中没有指定方案,则返回默认文件系统;
    • 第三个方法给定特定用户来访问文件系统,这对安全至关重要。
  • fileSystem.open(new Path(“/input/wordcount.txt”));用来打开一个hdfs文件的输入流

  • IOUtils.copyBytes(in, System.out, 4096, true)

    • 第一个参数是刚刚打开的输入流;
    • 第二个参数是输出流;
    • 第三个参数是缓冲区大小,这里设定为4K
    • 第四个参数是是否关闭流,包括输入流和输出流,如果需要自己处理,这里传入false即可。
  • 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);
    }

查询文件系统,FileStatus

  • FIleStatus,文件元数据
    • 任何文件系统的一个重要特征都是提供其目录结构浏览和检索它所存储文件和目录相关信息的功能。FileStatus类封装了文件系统中文件和目录的元数据,包括文件长度、块大小、副本、修改时间、所有者以及权限信息等等。
    • FileSystem.getFileStatus()方法可以获取文件或者目录的FileStatus对象,示例如下
// 对于文件夹和文件,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());
    }
  • 当上边方法的url是文件夹时:

这里写图片描述

  • 当上边方法的URL是文件时:

这里写图片描述

  • 查询文件列表的方法如下:
    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开发任务通过本文完全没有问题。但是对于文件读取和文件写入时的数据流分析的内容,本章没有多做说明,主要是我只是粗略阅读了这部分内容,不敢妄加整理;二来最近项目工期紧,数据流的知识在后期会补上。

猜你喜欢

转载自blog.csdn.net/maniacer/article/details/80149447
今日推荐