Hadoop HDFS笔记

Hadoop HDFS笔记

参考资料:

一、简介

HDFS(Hadoop Distributed File System)是Hadoop项目的核心子项目,是分布式计算中数据存储管理的基础,是基于流数据模式访问和处理超大文件的需求而开发的,可以运行于廉价的商用服务器上。它所具有的高容错、高可靠性、高可扩展性、高获得性、高吞吐率等特征为海量数据提供了不怕故障的存储,为超大数据集(Large Data Set)的应用处理带来了很多便利。

二、优缺点

优点

1、高容错性

  • 数据自动保存多个副本。它通过增加副本的形式,提高容错性。
  • 某一个副本丢失以后,它可以自动恢复,这是由 HDFS 内部机制实现的,我们不必关心。

2、适合批处理

  • 它是通过移动计算而不是移动数据。
  • 它会把数据位置暴露给计算框架。

3、适合大数据处理

  • 处理数据达到 GB、TB、甚至PB级别的数据。
  • 能够处理百万规模以上的文件数量,数量相当之大。
  • 能够处理10K节点的规模。

4、流式文件访问

  • 一次写入,多次读取。文件一旦写入不能修改,只能追加。
  • 它能保证数据的一致性。

5、可构建在廉价机器上

  • 它通过多副本机制,提高可靠性。
  • 它提供了容错和恢复机制。比如某一个副本丢失,可以通过其它副本来恢复。

不适用场景

1、低延时数据访问

  • 比如毫秒级的来存储数据,这是不行的,它做不到。
  • 它适合高吞吐率的场景,就是在某一时间内写入大量的数据。但是它在低延时的情况下是不行的,比如毫秒级以内读取数据,这样它是很难做到的。

2、小文件存储

  • 存储大量小文件(这里的小文件是指小于HDFS系统的Block大小的文件(默认64M))的话,它会占用 NameNode大量的内存来存储文件、目录和块信息。这样是不可取的,因为NameNode的内存总是有限的。
  • 小文件存储的寻道时间会超过读取时间,它违反了HDFS的设计目标。

3、并发写入、文件随机修改

  • 一个文件只能有一个写,不允许多个线程同时写。
  • 仅支持数据 append(追加),不支持文件的随机修改。

三、HDFS架构

HDFS的架构图

HDFS 采用Master/Slave的架构来存储数据,这种架构主要由四个部分组成,分别为HDFS Client、NameNode、DataNode和Secondary NameNode。下面我们分别介绍这四个组成部分:

1、Client:客户端

  • 文件切分。文件上传 HDFS 的时候,Client 将文件切分成 一个一个的Block,然后进行存储。
  • 与 NameNode 交互,获取文件的位置信息。
  • 与 DataNode 交互,读取或者写入数据。
  • Client 提供一些命令来管理 HDFS,比如启动或者关闭HDFS。
  • Client 可以通过一些命令来访问 HDFS。

HDFS中的文件在物理上是分块存储(block),作为独立的存储单元,称为数据块,块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M。

使用数据块的好处是:

  • 一个文件的大小可以大于网络中任意一个磁盘的容量。文件的所有块不需要存储在同一个磁盘上,因此它们可以利用集群上的任意一个磁盘进行存储。
  • 简化了存储子系统的设计,将存储子系统控制单元设置为块,可简化存储管理,同时元数据就不需要和块一同存储,用一个单独的系统就可以管理这些块的元数据。
  • 数据块适合用于数据备份进而提供数据容错能力和提高可用性。

HDFS的Block为什么这么大?

是为了最小化查找(seek)时间,控制定位文件与传输文件所用的时间比例。假设定位到Block所需的时间为10ms,磁盘传输速度为100M/s。如果要将定位到Block所用时间占传输时间的比例控制1%,则Block大小需要约100M。

但是如果Block设置过大,在MapReduce任务中,Map或者Reduce任务的个数如果小于集群机器数量,会使得作业运行效率很低。

2、NameNode:master

HDFS的守护进程,它是一个主管、管理者。

  • 管理 HDFS 的命名空间
  • 管理数据块(Block)映射信息,即记录文件是如何分割成数据块
  • 配置副本策略
  • 处理客户端读写请求。

3、DataNode:Slave

HDFS的工作节点。NameNode 下达命令,DataNode 执行实际的操作。根据需要存储和检索数据块,并且定期向namenode发送他们所存储的块的列表。

  • 存储实际的数据块。
  • 执行数据块的读/写操作。

4、Secondary NameNode:辅助后台程序

Secondary NameNode并非 NameNode 的热备。当NameNode 挂掉的时候,它并不能马上替换 NameNode 并提供服务。

  • 辅助 NameNode,分担其工作量。
  • 定期合并 fsimage和fsedits,并推送给NameNode。
  • 在紧急情况下,可辅助恢复 NameNode。

合并触发机制:1、超过3600s合并一次;2、edits文件大小超过64M

fsimage:元数据镜像文件(文件系统的目录树)

edits:元数据的操作日志(针对文件系统做的修改操作记录)

namenode内存中存储的是=fsimage+edits。

热备份:b是a的热备份,如果a坏掉。那么b马上运行代替a的工作。

冷备份:b是a的冷备份,如果a坏掉。那么b不能马上代替a工作。但是b上存储a的一些信息,减少a坏掉之后的损失。

四、HDFS读取文件

HDFS的文件读取原理,主要包括以下几个步骤:

  1. 首先客户端调用FileSystem对象的open方法在HDFS中打开要读取的文件,其实获取的是一个DistributedFileSystem的实例。
  2. DistributedFileSystem通过RPC(远程过程调用)来调用namenode,确定文件起始块的位置,即获得文件的第一批block的locations,同一block按照重复数会返回多个locations,这些locations按照hadoop拓扑结构排序,距离客户端近的排在前面。
  3. 前两步会返回一个支持文件定位的输入流 FSDataInputStream对象,该对象会被封装成 DFSInputStream对象(存储着文件起始几个块的datanode地址),DFSInputStream可以方便的管理datanode和namenode数据流。客户端调用read方法,DFSInputStream就会找出离客户端最近的datanode并连接datanode。
  4. 数据从datanode源源不断的流向客户端
  5. 如果第一个block块的数据读完了,就会关闭指向第一个block块的datanode连接,接着读取下一个block块。这些操作对客户端来说是透明的,从客户端的角度来看只是读一个持续不断的流。
  6. 如果第一批block都读完了,DFSInputStream就会去namenode拿下一批blocks的location,然后继续读,如果所有的block块都读完,这时就会关闭掉所有的流

在读数据过程中,如果与Datanode的通信发生错误,DFSInputStream对象会尝试从下一个最佳节点读取数据,并且记住该失败节点, 后续Block的读取不会再连接该节点。

读取一个Block之后,DFSInputStram会进行检验和验证,如果Block损坏,尝试从其他节点读取数据,并且将损坏的block汇报给Namenode。

读取和写入的过程中,namenode在分配Datanode的时候,会考虑节点之间的距离。HDFS中,距离没有采用带宽来衡量,因为实际中很难准确度量两台机器之间的带宽。

Hadoop把机器之间的拓扑结构组织成树结构,并且用到达公共父节点所需跳转数之和作为距离。事实上这是一个距离矩阵的例子。

同一数据中心,同一机架,同一节点距离为0

同一数据中心,同一机架,不同节点距离为2

同一数据中心,不同机架,不同节点距离为4

不同数据中心,不同机架,不同节点距离为6

Hadoop集群的拓扑结构需要手动配置,如果没配置,Hadoop默认所有节点位于同一个数据中心的同一机架上。

五、HDFS写入文件

HDFS的文件写入原理,主要包括以下几个步骤:

  1. 客户端通过调用 DistributedFileSystem 的create方法,创建一个新的文件
  2. DistributedFileSystem 通过 RPC(远程过程调用)调用 NameNode,去创建一个没有blocks关联的新文件。创建前,NameNode 会做各种校验,比如文件是否存在,客户端有无权限去创建等。如果校验通过,NameNode 就会记录下新文件,否则就会抛出IO异常。
  3. 前两步结束后会返回 FSDataOutputStream 的对象,和读文件的时候相似,FSDataOutputStream 被封装成 DFSOutputStream,DFSOutputStream 负责处理namenode和datanode之间的通信。客户端开始写数据到DFSOutputStream,DFSOutputStream会把数据切成一个个小packet(64k),然后排成队列 data queue。 使用管道 与 切割成packet的理由:并行存储,增加效率。
  4. DataStreamer 会去处理接受 data queue,它先问询 NameNode 这个新的 block 最适合存储的在哪几个DataNode里,比如重复数是3,那么就找到3个最适合的 DataNode,把它们排成一个 pipeline。DataStreamer 把 packet 按队列输出到管道的第一个 DataNode 中,第一个 DataNode又把 packet 输出到第二个 DataNode 中,以此类推。
  5. DFSOutputStream 还有一个队列叫 ack queue,也是由 packet 组成,等待DataNode的收到响应,当pipeline中的所有DataNode都表示已经收到的时候,这时akc queue才会把对应的packet包移除掉
  6. 客户端完成写数据后,调用close方法关闭写入流
  7. DataStreamer 把剩余的包都刷到 pipeline 里,然后等待 ack 信息,收到最后一个 ack 后,通知 DataNode 把文件标示为已完成

HDFS一致性:HDFS在写数据务必要保证数据的一致性与持久性,目前HDFS提供的两个保证数据一致性的方法:hsync()方法和hflush()方法。

  • hflush: 保证flush的数据被新的reader读到,但是不保证数据被datanode持久化。
  • hsync: 与hflush几乎一样,不同的是hsync保证数据被datanode持久化。

使用hflush或hsync会导致吞吐量下降,因此设计应用时,需要在吞吐量以及数据的健壮性之间做权衡。

另外,文件写入过程中,当前正在写入的Block对其他Reader不可见。

六、HDFS副本存放策略

namenode如何选择在哪个datanode 存储副本(replication)?这里需要对可靠性、写入带宽和读取带宽进行权衡。Hadoop对datanode存储副本有自己的副本策略,在其发展过程中一共有两个版本的副本策略,分别如下所示

这样选择很好地平衡了可靠性、读写性能

  • 可靠性:Block分布在两个机架上
  • 写带宽:写入管道的过程只需要跨越一个交换机
  • 读带宽:可以从两个机架中任选一个读取

七、hadoop2.x新特性

  • 引入了NameNode Federation,解决了横向内存扩展
  • 引入了Namenode HA,解决了namenode单点故障(SPOF: Single Point Of Failure)
  • 引入了YARN,负责资源管理和调度
  • 增加了ResourceManager HA解决了ResourceManager单点故障

1、NameNode Federation

架构如下图

  • 存在多个NameNode,每个NameNode分管一部分目录。每个namenode维护一个命名空间卷,包括命名空间的源数据和该命名空间下的文件的所有数据块的数据块池。
  • NameNode共用DataNode

这样做的好处就是当NN内存受限时,能扩展内存,解决内存扩展问题,而且每个NN独立工作相互不受影响,比如其中一个NN挂掉啦,它不会影响其他NN提供服务,但我们需要注意的是,虽然有多个NN,分管不同的目录,但是对于特定的NN,依然存在单点故障,因为没有它没有热备,解决单点故障使用NameNode HA。

2、NameNode HA

解决方案:

  • 基于NFS共享存储解决方案
  • 基于Qurom Journal Manager(QJM)解决方案
(1)基于NFS方案

Active NN与Standby NN通过NFS实现共享数据,但如果Active NN与NFS之间或Standby NN与NFS之间,其中一处有网络故障的话,那就会造成数据同步问题。

(2)基于QJM方案

架构如下图

  • Active NN、Standby NN有主备之分,NN Active是主的,NN Standby备用的。

  • 集群启动之后,一个namenode是active状态,来处理client与datanode之间的请求,并把相应的日志文件写到本地中或JN(journalNode)中;

  • Active NN与Standby NN之间是通过一组JN共享数据(JN一般为奇数个,ZK一般也为奇数个),Active NN会把日志文件、镜像文件写到JN中去,只要JN中有一半写成功,那就表明Active NN向JN中写成功啦,Standby NN就开始从JN中读取数据,来实现与Active NN数据同步,这种方式支持容错,因为Standby NN在启动的时候,会加载镜像文件(fsimage)并周期性的从JN中获取日志文件来保持与Active NN同步。

  • 为了实现Standby NN在Active NN挂掉之后,能迅速的再提供服务,需要DN不仅需要向Active NN汇报,同时还要向Standby NN汇报,这样就使得Standby NN能保存数据块在DN上的位置信息,因为在NameNode在启动过程中最费时工作,就是处理所有DN上的数据块的信息。

  • 为了实现Active NN高热备,增加了FailoverController和ZK,FailoverController通过Heartbeat的方式与ZK通信,通过ZK来选举,一旦Active NN挂掉,就选取另一个FailoverController作为active状态,然后FailoverController通过rpc,让standby NN转变为Active NN。

  • FailoverController一方面监控NN的状态信息,一方面还向ZK定时发送心跳,使自己被选举。当自己被选为主(Active)的时候,就会通过rpc使相应NN转变Active状态。

3、结合HDFS2的新特性,在实际生成环境中部署图

这里有12个DN,有4个NN,NN-1与NN-2是主备关系,它们管理/share目录;NN-3与NN-4是主备关系,它们管理/user目录

八、常用shell命令

启动HDFS

最初,你必须格式化配置HDFS文件系统,打开namenode(HDFS服务器), 然后执行以下命令。

$ hadoop namenode -format

格式化HDFS后,启动分布式文件系统。以下命令将启动namenode以及数据节点作为集群。

$ start-dfs.sh

你可以使用以下命令关闭HDFS。

$ stop-dfs.sh

  • 获取所有的命令及其解释:hadoop fs -help
  • 列出hdfs文件系统根目录下的目录和文件:hadoop fs -ls /
  • 在hdfs中新建文件夹:hadoop fs -mkdir <hdfs dir>
  • 删除文件或文件夹及文件夹下的文件:hadoop fs -rm -r <hdfs dir or file>
  • 从本地文件系统将一个文件复制到HDFS:hadoop fs -copyFromLocal <local path> <hdfs path>
  • put等同于copyFromLocal:hadoop fs -put <local path> <hdfs path>
  • get等同于copyToLocal,就是从hdfs下载文件到本地:hadoop fs -get <localpath>
  • 从hdfs的一个路径拷贝hdfs的另一个路径:hadoop fs -cp <path1> <path2>
  • 在hdfs目录中移动文件:hadoop fs -mv <path1> <path2>
  • 从本地剪切粘贴到hdfs:hadoop fs -moveFromLocal <local path> <hdfs path>
  • 从hdfs剪切粘贴到本地:hadoop fs -moveToLocal <hdfs path> <local path>
  • 追加一个文件到已经存在的文件末尾:hadoop fs -appendToFile <filepath1> <filepath2>
  • getmerge合并下载多个文件:hadoop fs -getmerge /aaa/log.* ./log.sum
  • 显示文件内容:hadoop fs -cat <filepath>
  • 显示一个文件的末尾:hadoop fs -tail <filepath>
  • 以字符形式打印一个文件的内容:hadoop fs -text <filepath>
  • chgrp、chmod、chown,inux文件系统中的用法一样,对文件所属权限
  • 统计文件系统的可用空间信息:hadoop fs -df -h <dirpath>
  • 统计文件夹的大小信息:hadoop fs -du -s -h <dirpath>
  • 统计一个指定目录下的文件节点数量:hadoop fs -count <dirpath>
  • 设置hdfs中文件的副本数量:hadoop fs -setrep 3 <filepath>

九、HDFS客户端操作数据Java代码示例

1、从Hadoop URL读取数据

package filesystem;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
import org.apache.hadoop.io.IOUtils;

public class URLCat {
    static {
        URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
    }
    public static void main(String[] args) throws MalformedURLException, IOException {
        InputStream in = null;
        String input = "hdfs://192.168.92.138:9000/user/test.txt";
        try {
            in = new URL(input).openStream();
            IOUtils.copyBytes(in, System.out, 4096,false);
        }finally {
            IOUtils.closeStream(in);
        }
    }
}

2、通过FileSystem API读取数据

package filesystem;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;

public class FileSystemCat {

    public static void main(String[] args) throws IOException {
        String uri = "hdfs://192.168.92.136:9000/user/test.txt";
        Configuration conf = new Configuration();

        FileSystem fs = FileSystem.get(URI.create(uri),conf);
        InputStream in = null;
        try {
            in = fs.open(new Path(uri));
            IOUtils.copyBytes(in, System.out, 1024,false);
        }finally {
            IOUtils.closeStream(in);
        }
    }
}

这里调用open()函数来获取文件的输入流,FileSystem的get()方法获取FileSystem实例。

3、使用FileSystem API写入数据

package filesystem;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.Progressable;

public class FileCopyWithProgress {

    public static void main(String[] args) throws Exception {
        String localSrc = "E:\\share\\input\\2007_12_1.txt";
        String dst = "hdfs://192.168.92.136:9000/user/logs/2008_10_2.txt";

        InputStream in = new BufferedInputStream(new FileInputStream(localSrc));

        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(URI.create(dst),conf);
        OutputStream out = fs.create(new Path(dst),new Progressable() {
            public void progress() {
                System.out.print("*");
            }
        });
        IOUtils.copyBytes(in, out, 1024,true);
    }
}

FileSystem的create()方法用于新建文件,返回FSDataOutputStream对象。 Progressable()用于传递回调窗口,可以用来把数据写入datanode的进度通知给应用。

4、使用FileSystem API删除数据

package filesystem;

import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class FileDelete {

    public static void main(String[] args) throws Exception{
        String uri = "hdfs://192.168.92.136:9000/user/1400.txt";

        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(URI.create(uri),conf);
        fs.delete(new Path(uri));
    }
}

使用delete()方法来永久性删除文件或目录。

FileSystem的其它一些方法:

  • public boolean mkdirs(Path f) throws IOException 用来创建目录,创建成功返回true。
  • public FileStatus getFileStates(Path f) throws FIleNotFoundException 用来获取文件或目录的FileStatus对象。
  • public FileStatus[ ] listStatus(Path f)throws IOException 列出目录中的内容
  • public FileStatus[ ] globStatu(Path pathPattern) throws IOException 返回与其路径匹配于指定模式的所有文件的FileStatus对象数组,并按路径排序

5、文件的增删改查示例

public class HdfsClient {

    FileSystem fs = null;

    @Before
    public void init() throws Exception {
        // 构造一个配置参数对象,设置一个参数:我们要访问的hdfs的URI
        // 从而FileSystem.get()方法就知道应该是去构造一个访问hdfs文件系统的客户端,以及hdfs的访问地址
        // new Configuration();的时候,它就会去加载jar包中的hdfs-default.xml
        // 然后再加载classpath下的hdfs-site.xml
        Configuration conf = new Configuration();
        conf.set("fs.defaultFS", "hdfs://hdp-node01:9000");
        /**
         * 参数优先级: 1、客户端代码中设置的值 2、classpath下的用户自定义配置文件 3、然后是服务器的默认配置
         */
        conf.set("dfs.replication", "3");
        // 获取一个hdfs的访问客户端,根据参数,这个实例应该是DistributedFileSystem的实例
        // fs = FileSystem.get(conf);
        // 如果这样去获取,那conf里面就可以不要配"fs.defaultFS"参数,而且,这个客户端的身份标识已经是hadoop用户
        fs = FileSystem.get(new URI("hdfs://hdp-node01:9000"), conf, "hadoop");
    }

    /**
     * 往hdfs上传文件
     *
     * @throws Exception
     */
    @Test
    public void testAddFileToHdfs() throws Exception {
        // 要上传的文件所在的本地路径
        Path src = new Path("g:/redis-recommend.zip");
        // 要上传到hdfs的目标路径
        Path dst = new Path("/aaa");
        fs.copyFromLocalFile(src, dst);
        fs.close();
    }

    /**
     * 从hdfs中复制文件到本地文件系统
     *
     * @throws IOException
     * @throws IllegalArgumentException
     */
    @Test
    public void testDownloadFileToLocal() throws IllegalArgumentException, IOException {
        fs.copyToLocalFile(new Path("/jdk-7u65-linux-i586.tar.gz"), new Path("d:/"));
        fs.close();
    }

    @Test
    public void testMkdirAndDeleteAndRename() throws IllegalArgumentException, IOException {
        // 创建目录
        fs.mkdirs(new Path("/a1/b1/c1"));
        // 删除文件夹 ,如果是非空文件夹,参数2必须给值true
        fs.delete(new Path("/aaa"), true);
        // 重命名文件或文件夹
        fs.rename(new Path("/a1"), new Path("/a2"));
    }

    /**
     * 查看目录信息,只显示文件
     *
     * @throws IOException
     * @throws IllegalArgumentException
     * @throws FileNotFoundException
     */
    @Test
    public void testListFiles() throws FileNotFoundException, IllegalArgumentException, IOException {
        // 思考:为什么返回迭代器,而不是List之类的容器
        RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
        while (listFiles.hasNext()) {
            LocatedFileStatus fileStatus = listFiles.next();
            System.out.println(fileStatus.getPath().getName());
            System.out.println(fileStatus.getBlockSize());
            System.out.println(fileStatus.getPermission());
            System.out.println(fileStatus.getLen());
            BlockLocation[] blockLocations = fileStatus.getBlockLocations();
            for (BlockLocation bl : blockLocations) {
                System.out.println("block-length:" + bl.getLength() + "--" + "block-offset:" + bl.getOffset());
                String[] hosts = bl.getHosts();
                for (String host : hosts) {
                    System.out.println(host);
                }
            }
            System.out.println("--------------打印的分割线--------------");
        }
    }

    /**
     * 查看文件及文件夹信息
     *
     * @throws IOException
     * @throws IllegalArgumentException
     * @throws FileNotFoundException
     */
    @Test
    public void testListAll() throws FileNotFoundException, IllegalArgumentException, IOException {
        FileStatus[] listStatus = fs.listStatus(new Path("/"));
        String flag = "d--             ";
        for (FileStatus fstatus : listStatus) {
            if (fstatus.isFile()) flag = "f--         ";
            System.out.println(flag + fstatus.getPath().getName());
        }
    }
}

6、通过流的方式访问hdfs

/**
 * 相对那些封装好的方法而言的更底层一些的操作方式
 * <p>
 * 上层那些mapreduce   spark等运算框架,去hdfs中获取数据的时候,就是调的这种底层的api
 *
 * @author
 */

public class StreamAccess {

    FileSystem fs = null;

    @Before
    public void init() throws Exception {
        Configuration conf = new Configuration();
        fs = FileSystem.get(new URI("hdfs://hdp-node01:9000"), conf, "hadoop");
    }

    @Test
    public void testDownLoadFileToLocal() throws IllegalArgumentException, IOException {
        //先获取一个文件的输入流----针对hdfs上的
        FSDataInputStream in = fs.open(new Path("/jdk-7u65-linux-i586.tar.gz"));
        //再构造一个文件的输出流----针对本地的
        FileOutputStream out = new FileOutputStream(new File("c:/jdk.tar.gz"));
        //再将输入流中数据传输到输出流
        IOUtils.copyBytes(in, out, 4096);
    }

    /**
     * hdfs支持随机定位进行文件读取,而且可以方便地读取指定长度
     * <p>
     * 用于上层分布式运算框架并发处理数据
     *
     * @throws IllegalArgumentException
     * @throws IOException
     */
    @Test
    public void testRandomAccess() throws IllegalArgumentException, IOException {
        //先获取一个文件的输入流----针对hdfs上的
        FSDataInputStream in = fs.open(new Path("/iloveyou.txt"));
        //可以将流的起始偏移量进行自定义
        in.seek(22);
        //再构造一个文件的输出流----针对本地的
        FileOutputStream out = new FileOutputStream(new File("c:/iloveyou.line.2.txt"));
        IOUtils.copyBytes(in, out, 19L, true);
    }

    /**
     * 显示hdfs上文件的内容
     *
     * @throws IOException
     * @throws IllegalArgumentException
     */
    @Test
    public void testCat() throws IllegalArgumentException, IOException {
        FSDataInputStream in = fs.open(new Path("/iloveyou.txt"));
        IOUtils.copyBytes(in, System.out, 1024);
    }
}

十、 相关运维工具

使用distcp并行复制

Hadoop提供的distcp工具用于并行导入数据到Hadoop或者从Hadoop导出。一些例子:

hadoop distcp file1 file2  //可以作为fs -cp命令的高效替代
hadoop distcp dir1 dir2
hadoop distcp -update dir1 dir2 #update参数表示只同步被更新的文件,其他保持不变

distcp是底层使用MapReduce实现,只有map实现,没有reduce。在map中并行复制文件。 distcp尽可能在map之间平均分配文件。map的数量可以通过-m参数指定:

hadoop distcp -update -delete -p hdfs://master1:9000/foo hdfs://master2/foo

这样的操作常用于在两个集群之间复制数据,update参数表示只同步被更新过的数据,delete会删除目标目录中存在,但是源目录不存在的文件。p参数表示保留文件的全校、block大小、副本数量等属性。

如果两个集群的Hadoop版本不兼容,可以使用webhdfs协议:

hadoop distcp webhdfs://namenode1:50070/foo webhdfs://namenode2:50070/foo

平衡HDFS集群

在distcp工具中,如果我们指定map数量为1,不仅速度很慢,每个Block第一个副本将全部落到运行这个唯一map的节点上,直到磁盘溢出。因此使用distcp的时候,最好使用默认的map数量,即20。

HDFS在Block均匀分布在各个节点上的时候工作得最好,如果没有办法在作业中尽量保持集群平衡,例如为了限制map数量(以便其他节点可以被别的作业使用),那么可以使用balancer工具来调整集群的Block分布。

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值