HOME 首页
SERVICE 服务产品
XINMEITI 新媒体代运营
CASE 服务案例
NEWS 热点资讯
ABOUT 关于我们
CONTACT 联系我们
创意岭
让品牌有温度、有情感
专注品牌策划15年

    请简述一下HDFS数据读取和写入流程(请简述hdfs的读写流程)

    发布时间:2023-04-19 09:16:50     稿源: 创意岭    阅读: 65        

    大家好!今天让创意岭的小编来大家介绍下关于请简述一下HDFS数据读取和写入流程的问题,以下是小编对此问题的归纳整理,让我们一起来看看吧。

    开始之前先推荐一个非常厉害的Ai人工智能工具,一键生成原创文章、方案、文案、工作计划、工作报告、论文、代码、作文、做题和对话答疑等等

    只需要输入关键词,就能返回你想要的内容,越精准,写出的就越详细,有微信小程序端、在线网页版、PC客户端

    官网:https://ai.de1919.com

    创意岭作为行业内优秀的企业,服务客户遍布全球各地,如需了解SEO相关业务请拨打电话175-8598-2043,或添加微信:1454722008

    本文目录:

    请简述一下HDFS数据读取和写入流程(请简述hdfs的读写流程)

    一、Hadoop读写文件时内部工作机制是怎样的

    客户端通过调用FileSystem对象(对应于HDFS文件系统,调用DistributedFileSystem对象)的open()方法来打开文件(也即图中的第一步),DistributedFileSystem通过RPC(Remote Procedure Call)调用询问NameNode来得到此文件最开始几个block的文件位置(第二步)。对每一个block来说,namenode返回拥有此block备份的所有namenode的地址信息(按集群的拓扑网络中与客户端距离的远近排序,关于在Hadoop集群中如何进行网络拓扑请看下面介绍)。如果客户端本身就是一个datanode(如客户端是一个mapreduce任务)并且此datanode本身就有所需文件block的话,客户端便从本地读取文件。

    以上步骤完成后,DistributedFileSystem会返回一个FSDataInputStream(支持文件seek),客户端可以从FSDataInputStream中读取数据。FSDataInputStream包装了一个DFSInputSteam类,用来处理namenode和datanode的I/O操作。

    客户端然后执行read()方法(第三步),DFSInputStream(已经存储了欲读取文件的开始几个block的位置信息)连接到第一个datanode(也即最近的datanode)来获取数据。通过重复调用read()方法(第四、第五步),文件内的数据就被流式的送到了客户端。当读到该block的末尾时,DFSInputStream就会关闭指向该block的流,转而找到下一个block的位置信息然后重复调用read()方法继续对该block的流式读取。这些过程对于用户来说都是透明的,在用户看来这就是不间断的流式读取整个文件。

    当真个文件读取完毕时,客户端调用FSDataInputSteam中的close()方法关闭文件输入流(第六步)。

    如果在读某个block是DFSInputStream检测到错误,DFSInputSteam就会连接下一个datanode以获取此block的其他备份,同时他会记录下以前检测到的坏掉的datanode以免以后再无用的重复读取该datanode。DFSInputSteam也会检查从datanode读取来的数据的校验和,如果发现有数据损坏,它会把坏掉的block报告给namenode同时重新读取其他datanode上的其他block备份。

    这种设计模式的一个好处是,文件读取是遍布这个集群的datanode的,namenode只是提供文件block的位置信息,这些信息所需的带宽是很少的,这样便有效的避免了单点瓶颈问题从而可以更大的扩充集群的规模。

    Hadoop中的网络拓扑

    在Hadoop集群中如何衡量两个节点的远近呢?要知道,在高速处理数据时,数据处理速率的唯一限制因素就是数据在不同节点间的传输速度:这是由带宽的可怕匮乏引起的。所以我们把带宽作为衡量两个节点距离大小的标准。

    但是计算两个节点之间的带宽是比较复杂的,而且它需要在一个静态的集群下才能衡量,但Hadoop集群一般是随着数据处理的规模动态变化的(且两两节点直接相连的连接数是节点数的平方)。于是Hadoop使用了一个简单的方法来衡量距离,它把集群内的网络表示成一个树结构,两个节点之间的距离就是他们离共同祖先节点的距离之和。树一般按数据中心(datacenter),机架(rack),计算节点(datanode)的结构组织。计算节点上的本地运算速度最快,跨数据中心的计算速度最慢(现在跨数据中心的Hadoop集群用的还很少,一般都是在一个数据中心内做运算的)。

    假如有个计算节点n1处在数据中心c1的机架r1上,它可以表示为/c1/r1/n1,下面是不同情况下两个节点的距离:

    • distance(/d1/r1/n1, /d1/r1/n1) = 0 (processes on the same node)

    • distance(/d1/r1/n1, /d1/r1/n2) = 2 (different nodes on the same rack)

    • distance(/d1/r1/n1, /d1/r2/n3) = 4 (nodes on different racks in the same data center)

    • distance(/d1/r1/n1, /d2/r3/n4) = 6 (nodes in different data centers)

    如下图所示:

    Hadoop

    写文件

    现在我们来看一下Hadoop中的写文件机制解析,通过写文件机制我们可以更好的了解一下Hadoop中的一致性模型。

    Hadoop

    上图为我们展示了一个创建一个新文件并向其中写数据的例子。

    首先客户端通过DistributedFileSystem上的create()方法指明一个欲创建的文件的文件名(第一步),DistributedFileSystem再通过RPC调用向NameNode申请创建一个新文件(第二步,这时该文件还没有分配相应的block)。namenode检查是否有同名文件存在以及用户是否有相应的创建权限,如果检查通过,namenode会为该文件创建一个新的记录,否则的话文件创建失败,客户端得到一个IOException异常。DistributedFileSystem返回一个FSDataOutputStream以供客户端写入数据,与FSDataInputStream类似,FSDataOutputStream封装了一个DFSOutputStream用于处理namenode与datanode之间的通信。

    当客户端开始写数据时(第三步),DFSOutputStream把写入的数据分成包(packet), 放入一个中间队列——数据队列(data queue)中去。DataStreamer从数据队列中取数据,同时向namenode申请一个新的block来存放它已经取得的数据。namenode选择一系列合适的datanode(个数由文件的replica数决定)构成一个管道线(pipeline),这里我们假设replica为3,所以管道线中就有三个datanode。DataSteamer把数据流式的写入到管道线中的第一个datanode中(第四步),第一个datanode再把接收到的数据转到第二个datanode中(第四步),以此类推。

    DFSOutputStream同时也维护着另一个中间队列——确认队列(ack queue),确认队列中的包只有在得到管道线中所有的datanode的确认以后才会被移出确认队列(第五步)。

    如果某个datanode在写数据的时候当掉了,下面这些对用户透明的步骤会被执行:

    1)管道线关闭,所有确认队列上的数据会被挪到数据队列的首部重新发送,这样可以确保管道线中当掉的datanode下流的datanode不会因为当掉的datanode而丢失数据包。

    2)在还在正常运行的datanode上的当前block上做一个标志,这样当当掉的datanode重新启动以后namenode就会知道该datanode上哪个block是刚才当机时残留下的局部损坏block,从而可以把它删掉。

    3)已经当掉的datanode从管道线中被移除,未写完的block的其他数据继续被写入到其他两个还在正常运行的datanode中去,namenode知道这个block还处在under-replicated状态(也即备份数不足的状态)下,然后他会安排一个新的replica从而达到要求的备份数,后续的block写入方法同前面正常时候一样。

    有可能管道线中的多个datanode当掉(虽然不太经常发生),但只要dfs.replication.min(默认为1)个replica被创建,我们就认为该创建成功了。剩余的replica会在以后异步创建以达到指定的replica数。

    当客户端完成写数据后,它会调用close()方法(第六步)。这个操作会冲洗(flush)所有剩下的package到pipeline中,等待这些package确认成功,然后通知namenode写入文件成功(第七步)。这时候namenode就知道该文件由哪些block组成(因为DataStreamer向namenode请求分配新block,namenode当然会知道它分配过哪些blcok给给定文件),它会等待最少的replica数被创建,然后成功返回。

    replica是如何分布的

    Hadoop在创建新文件时是如何选择block的位置的呢,综合来说,要考虑以下因素:带宽(包括写带宽和读带宽)和数据安全性。如果我们把三个备份全部放在一个datanode上,虽然可以避免了写带宽的消耗,但几乎没有提供数据冗余带来的安全性,因为如果这个datanode当机,那么这个文件的所有数据就全部丢失了。另一个极端情况是,如果把三个冗余备份全部放在不同的机架,甚至数据中心里面,虽然这样数据会安全,但写数据会消耗很多的带宽。Hadoop 0.17.0给我们提供了一个默认replica分配策略(Hadoop 1.X以后允许replica策略是可插拔的,也就是你可以自己制定自己需要的replica分配策略)。replica的默认分配策略是把第一个备份放在与客户端相同的datanode上(如果客户端在集群外运行,就随机选取一个datanode来存放第一个replica),第二个replica放在与第一个replica不同机架的一个随机datanode上,第三个replica放在与第二个replica相同机架的随机datanode上。如果replica数大于三,则随后的replica在集群中随机存放,Hadoop会尽量避免过多的replica存放在同一个机架上。选取replica的放置位置后,管道线的网络拓扑结构如下所示:

    Hadoop

    总体来说,上述默认的replica分配策略给了我们很好的可用性(blocks放置在两个rack上,较为安全),写带宽优化(写数据只需要跨越一个rack),读带宽优化(你可以从两个机架中选择较近的一个读取)。

    一致性模型

    HDFS某些地方为了性能可能会不符合POSIX(是的,你没有看错,POSIX不仅仅只适用于linux/unix, Hadoop 使用了POSIX的设计来实现对文件系统文件流的读取 ),所以它看起来可能与你所期望的不同,要注意。

    创建了一个文件以后,它是可以在命名空间(namespace)中可以看到的:

    Path p = new Path("p");

    fs.create(p);

    assertThat(fs.exists(p), is(true));

    但是任何向此文件中写入的数据并不能保证是可见的,即使你flush了已经写入的数据,此文件的长度可能仍然为零:

    Path p = new Path("p");

    OutputStream out = fs.create(p);

    out.write("content".getBytes("UTF-8"));

    out.flush();

    assertThat(fs.getFileStatus(p).getLen(), is(0L));

    这是因为,在Hadoop中,只有满一个block数据量的数据被写入文件后,此文件中的内容才是可见的(即这些数据会被写入到硬盘中去),所以当前正在写的block中的内容总是不可见的。

    Hadoop提供了一种强制使buffer中的内容冲洗到datanode的方法,那就是FSDataOutputStream的sync()方法。调用了sync()方法后,Hadoop保证所有已经被写入的数据都被冲洗到了管道线中的datanode中,并且对所有读者都可见了:

    Path p = new Path("p");

    FSDataOutputStream out = fs.create(p);

    out.write("content".getBytes("UTF-8"));

    out.flush();

    out.sync();

    assertThat(fs.getFileStatus(p).getLen(), is(((long) "content".length())));

    这个方法就像POSIX中的fsync系统调用(它冲洗给定文件描述符中的所有缓冲数据到磁盘中)。例如,使用java API写一个本地文件,我们可以保证在调用flush()和同步化后可以看到已写入的内容:

    FileOutputStream out = new FileOutputStream(localFile);

    out.write("content".getBytes("UTF-8"));

    out.flush(); // flush to operating system

    out.getFD().sync(); // sync to disk (getFD()返回与该流所对应的文件描述符)

    assertThat(localFile.length(), is(((long) "content".length())));

    在HDFS中关闭一个流隐式的调用了sync()方法:

    Path p = new Path("p");

    OutputStream out = fs.create(p);

    out.write("content".getBytes("UTF-8"));

    out.close();

    assertThat(fs.getFileStatus(p).getLen(), is(((long) "content".length())));

    由于Hadoop中的一致性模型限制,如果我们不调用sync()方法的话,我们很可能会丢失多大一个block的数据。这是难以接受的,所以我们应该使用sync()方法来确保数据已经写入磁盘。但频繁调用sync()方法也是不好的,因为会造成很多额外开销。我们可以再写入一定量数据后调用sync()方法一次,至于这个具体的数据量大小就要根据你的应用程序而定了,在不影响你的应用程序的性能的情况下,这个数据量应越大越好。

    请简述一下HDFS数据读取和写入流程(请简述hdfs的读写流程)

    二、如何使用Hadoop读写数据库

    我们的一些应用程序中,常常避免不了要与数据库进行交互,而在我们的hadoop中,有时候也需要和数据库进行交互,比如说,数据分析的结果存入数据库,

    或者是,读取数据库的信息写入HDFS上,不过直接使用MapReduce操作数据库,这种情况在现实开发还是比较少,一般我们会采用Sqoop来进行数

    据的迁入,迁出,使用Hive分析数据集,大多数情况下,直接使用Hadoop访问关系型数据库,可能产生比较大的数据访问压力,尤其是在数据库还是单机

    的情况下,情况可能更加糟糕,在集群的模式下压力会相对少一些。

    那么,今天散仙就来看下,如何直接使用Hadoop1.2.0的MR来读写操作数据库,hadoop的API提供了DBOutputFormat和

    DBInputFormat这两个类,来进行与数据库交互,除此之外,我们还需要定义一个类似JAVA

    Bean的实体类,来与数据库的每行记录进行对应,通常这个类要实现Writable和DBWritable接口,来重写里面的4个方法以对应获取每行记

    三、怎样将数据库数据写入到hdfs中

    如下面这个shell脚本:

    #Oracle的连接字符串,其中包含了Oracle的地址,SID,和端口号

    CONNECTURL=jdbc:oracle:thin:@20.135.60.21:1521:DWRAC2

    #使用的用户名

    ORACLENAME=kkaa

    #使用的密码

    ORACLEPASSWORD=kkaa123

    #需要从Oracle中导入的表名

    oralceTableName=tt

    #需要从Oracle中导入的表中的字段名

    columns=AREA_ID,TEAM_NAME

    #将Oracle中的数据导入到HDFS后的存放路径

    hdfsPath=apps/as/hive/$oralceTableName

    #执行导入逻辑。将Oracle中的数据导入到HDFS中

    sqoop import --append --connect $CONNECTURL --username $ORACLENAME --password $ORACLEPASSWORD --target-dir $hdfsPath --num-mappers 1 --table $oralceTableName --columns $columns --fields-terminated-by '\001'

    执行这个脚本之后,导入程序就完成了。

    四、如何使用Java API读写HDFS

    Java API读写HDFS

    public class FSOptr {

    /**

    * @param args

    */

    public static void main(String[] args) throws Exception {

    // TODO Auto-generated method stub

    Configuration conf = new Configuration();

    makeDir(conf);

    rename(conf);

    delete(conf);

    }

    // 创建文件目录

    private static void makeDir(Configuration conf) throws Exception {

    FileSystem fs = FileSystem.get(conf);

    Path dir = new Path("/user/hadoop/data/20140318");

    boolean result = fs.mkdirs(dir);// 创建文件夹

    System.out.println("make dir :" + result);

    // 创建文件,并写入内容

    Path dst = new Path("/user/hadoop/data/20140318/tmp");

    byte[] buff = "hello,hadoop!".getBytes();

    FSDataOutputStream outputStream = fs.create(dst);

    outputStream.write(buff, 0, buff.length);

    outputStream.close();

    FileStatus files[] = fs.listStatus(dst);

    for (FileStatus file : files) {

    System.out.println(file.getPath());

    }

    fs.close();

    }

    // 重命名文件

    private static void rename(Configuration conf) throws Exception {

    FileSystem fs = FileSystem.get(conf);

    Path oldName = new Path("/user/hadoop/data/20140318/1.txt");

    Path newName = new Path("/user/hadoop/data/20140318/2.txt");

    fs.rename(oldName, newName);

    FileStatus files[] = fs.listStatus(new Path(

    "/user/hadoop/data/20140318"));

    for (FileStatus file : files) {

    System.out.println(file.getPath());

    }

    fs.close();

    }

    // 删除文件

    @SuppressWarnings("deprecation")

    private static void delete(Configuration conf) throws Exception {

    FileSystem fs = FileSystem.get(conf);

    Path path = new Path("/user/hadoop/data/20140318");

    if (fs.isDirectory(path)) {

    FileStatus files[] = fs.listStatus(path);

    for (FileStatus file : files) {

    fs.delete(file.getPath());

    }

    } else {

    fs.delete(path);

    }

    // 或者

    fs.delete(path, true);

    fs.close();

    }

    /**

    * 下载,将hdfs文件下载到本地磁盘

    *

    * @param localSrc1

    * 本地的文件地址,即文件的路径

    * @param hdfsSrc1

    * 存放在hdfs的文件地址

    */

    public boolean sendFromHdfs(String hdfsSrc1, String localSrc1) {

    Configuration conf = new Configuration();

    FileSystem fs = null;

    try {

    fs = FileSystem.get(URI.create(hdfsSrc1), conf);

    Path hdfs_path = new Path(hdfsSrc1);

    Path local_path = new Path(localSrc1);

    fs.copyToLocalFile(hdfs_path, local_path);

    return true;

    } catch (IOException e) {

    e.printStackTrace();

    }

    return false;

    }

    /**

    * 上传,将本地文件copy到hdfs系统中

    *

    * @param localSrc

    * 本地的文件地址,即文件的路径

    * @param hdfsSrc

    * 存放在hdfs的文件地址

    */

    public boolean sendToHdfs1(String localSrc, String hdfsSrc) {

    InputStream in;

    try {

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

    Configuration conf = new Configuration();// 得到配置对象

    FileSystem fs; // 文件系统

    try {

    fs = FileSystem.get(URI.create(hdfsSrc), conf);

    // 输出流,创建一个输出流

    OutputStream out = fs.create(new Path(hdfsSrc),

    new Progressable() {

    // 重写progress方法

    public void progress() {

    // System.out.println("上传完一个设定缓存区大小容量的文件!");

    }

    });

    // 连接两个流,形成通道,使输入流向输出流传输数据,

    IOUtils.copyBytes(in, out, 10240, true); // in为输入流对象,out为输出流对象,4096为缓冲区大小,true为上传后关闭流

    return true;

    } catch (IOException e) {

    e.printStackTrace();

    }

    } catch (FileNotFoundException e) {

    e.printStackTrace();

    }

    return false;

    }

    /**

    * 移动

    *

    * @param old_st原来存放的路径

    * @param new_st移动到的路径

    */

    public boolean moveFileName(String old_st, String new_st) {

    try {

    // 下载到服务器本地

    boolean down_flag = sendFromHdfs(old_st, "/home/hadoop/文档/temp");

    Configuration conf = new Configuration();

    FileSystem fs = null;

    // 删除源文件

    try {

    fs = FileSystem.get(URI.create(old_st), conf);

    Path hdfs_path = new Path(old_st);

    fs.delete(hdfs_path);

    } catch (IOException e) {

    e.printStackTrace();

    }

    // 从服务器本地传到新路径

    new_st = new_st + old_st.substring(old_st.lastIndexOf("/"));

    boolean uplod_flag = sendToHdfs1("/home/hadoop/文档/temp", new_st);

    if (down_flag && uplod_flag) {

    return true;

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    return false;

    }

    // copy本地文件到hdfs

    private static void CopyFromLocalFile(Configuration conf) throws Exception {

    FileSystem fs = FileSystem.get(conf);

    Path src = new Path("/home/hadoop/word.txt");

    Path dst = new Path("/user/hadoop/data/");

    fs.copyFromLocalFile(src, dst);

    fs.close();

    }

    // 获取给定目录下的所有子目录以及子文件

    private static void getAllChildFile(Configuration conf) throws Exception {

    FileSystem fs = FileSystem.get(conf);

    Path path = new Path("/user/hadoop");

    getFile(path, fs);

    }

    private static void getFile(Path path, FileSystem fs)throws Exception {

    FileStatus[] fileStatus = fs.listStatus(path);

    for (int i = 0; i < fileStatus.length; i++) {

    if (fileStatus[i].isDir()) {

    Path p = new Path(fileStatus[i].getPath().toString());

    getFile(p, fs);

    } else {

    System.out.println(fileStatus[i].getPath().toString());

    }

    }

    }

    //判断文件是否存在

    private static boolean isExist(Configuration conf,String path)throws Exception{

    FileSystem fileSystem = FileSystem.get(conf);

    return fileSystem.exists(new Path(path));

    }

    //获取hdfs集群所有主机结点数据

    private static void getAllClusterNodeInfo(Configuration conf)throws Exception{

    FileSystem fs = FileSystem.get(conf);

    DistributedFileSystem hdfs = (DistributedFileSystem)fs;

    DatanodeInfo[] dataNodeStats = hdfs.getDataNodeStats();

    String[] names = new String[dataNodeStats.length];

    System.out.println("list of all the nodes in HDFS cluster:"); //print info

    for(int i=0; i < dataNodeStats.length; i++){

    names[i] = dataNodeStats[i].getHostName();

    System.out.println(names[i]); //print info

    }

    }

    //get the locations of a file in HDFS

    private static void getFileLocation(Configuration conf)throws Exception{

    FileSystem fs = FileSystem.get(conf);

    Path f = new Path("/user/cluster/dfs.txt");

    FileStatus filestatus = fs.getFileStatus(f);

    BlockLocation[] blkLocations = fs.getFileBlockLocations(filestatus,0,filestatus.getLen());

    int blkCount = blkLocations.length;

    for(int i=0; i < blkCount; i++){

    String[] hosts = blkLocations[i].getHosts();

    //Do sth with the block hosts

    System.out.println(hosts);

    }

    }

    //get HDFS file last modification time

    private static void getModificationTime(Configuration conf)throws Exception{

    FileSystem fs = FileSystem.get(conf);

    Path f = new Path("/user/cluster/dfs.txt");

    FileStatus filestatus = fs.getFileStatus(f);

    long modificationTime = filestatus.getModificationTime(); // measured in milliseconds since the epoch

    Date d = new Date(modificationTime);

    System.out.println(d);

    }

    }

    以上就是关于请简述一下HDFS数据读取和写入流程相关问题的回答。希望能帮到你,如有更多相关问题,您也可以联系我们的客服进行咨询,客服也会为您讲解更多精彩的知识和内容。


    推荐阅读:

    请简述网络营销策略有哪几种(请简述网络营销策略有哪几种形式)

    请简述包装流程(简述包装工作流程)

    简述推销与营销的区别和联系(请简述推销与营销的区别和联系)

    广告高级背景图片素材(广告高级背景图片素材高清)

    家装报价清单明细表(全包装修价格一览表)