• HDFS:分布式文件系统


    HDFS是GFS的简化版,它同一时刻只允许一个用户对同一文件进行追加写操作(GFS允许并发写)。它适合存储大文件,并提供高吞吐量的顺序读/写访问。

    它的早期版本两大问题,例如:单点失效和水平扩展不佳。针对这两个问题,在hadoop2.0提出统一的解决方案,即HA和NameNode联盟。

    HDFS的设计目标

    • 存储大文件
    • 文件一次写(支持追加写)/顺序读

    HDFS优点:

    1. 适合大数据处理(支持GB,TB,PB级别的数据存储,支持百万规模以上的文件数量)
    2. 适合批处理(支持离线的批量数据处理,支持高吞吐率)
    3. 高容错性(以数据块存储,可以保存多个副本,容易实现负载均衡)

    HDFS缺点:

    1. 小文件存取(占用namenode大量内存,浪费磁盘空间)
    2. 不支持并发写入(同一时刻只能有一个进程写入,不支持随机修改)

    HDFS整体架构

    HDFS采用master/slave架构。一个HDFS集群包含一个单独的NameNode,一个Secondary NameNode和多个DataNode。
    image

    NameNode

    负责存储整个分布式文件系统的元数据,包括:

    1. 负责管理文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。
    2. 负责确定数据块到具体Datanode节点的映射(文件到块的映射,块到DataNode的映射)。
    3. 监督Data nodes的健康
    4. 协调数据的存取。

    这些数据保存在内存里,同时磁盘里面有两个元数据文件,fsimage和editlog,这两个结合可以构建完整的内存元数据。

    Name元数据大小估计

    对象类别 估算大小(bytes) 计算方法 估计总大小(bytes)
    文件 224 224+2*文件名长度 250
    目录 264 264+2*文件名长度 290
    152 152+72*副本数 368

    Secondary NameNode

    secondary NameNode并不是NameNode的热被,它负责拉取fsimage和editlog并且把它们合并形成新的fsimage并传回。所以本质上,Secondary NameNode是NameNode的一个检查点。

    DataNode

    DataNode保存文件数据,并负责数据块实际的读/写操作。HDFS会自动将用户上传的大文件切分为block,每个block默认大小是64M,每个block在HDFS中会备份三份

    Client

    Client和NameNode交互获得元数据,与DataNode交互进行实际的读写操作。

    HDFS写文件操作

    HDFS只允许同一时刻只有一个客户端对文件进行操作,同时HDFS支持追加写,不支持随机写

    image

    1. Client调用DistributedFileSystem对象的create方法,创建一个文件输出流(FSDataOutputStream)对象
    2. 通过DistributedFileSystem对象与Hadoop集群的NameNode进行一次RPC远程调用,在HDFS的Namespace中创建一个文件条目(Entry),该条目没有任何的Block
    3. 写文件前,向NameNode申请Block,NameNode返回可写文件的DataNode列表
    4. 通过FSDataOutputStream对象,向DataNode写入数据,数据首先被写入FSDataOutputStream对象内部的Buffer中,然后数据被分割成一个个Packet(64k)数据包
    5. 以Packet最小单位,基于Socket连接发送到按特定算法选择的HDFS集群中一组DataNode(正常是3个,可能大于等于1)中的一个节点上,在这组DataNode组成的Pipeline上依次传输Packet
    6. 这组DataNode组成的Pipeline反方向上,发送ack,最终由Pipeline中第一个DataNode节点将Pipeline ack发送给Client。
    7. 一个Block已经写入到DataNode节点磁盘,Client调用fsync让NameNode持久化Block的位置信息数据,DataNode也会通知NameNode成功持久化Block(这里存在一个不一致时间)。因为每个Block默认大小是64M,文件大于64M,跳到3。
    8. 完成向文件写入数据,Client在文件输出流(FSDataOutputStream)对象上调用close方法,关闭流
    9. 调用DistributedFileSystem对象的complete方法,通知NameNode文件写入成功
    static String[] contents = new String[] {
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
         "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
         "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
         "dddddddddddddddddddddddddddddddd",
         "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
    };
    
    public static void main(String[] args) {
         String file = "hdfs://h1:8020/data/test/test.log";
       Path path = new Path(file);
       Configuration conf = new Configuration();
       FileSystem fs = null;
       FSDataOutputStream output = null;
       try {
              fs = path.getFileSystem(conf);
              output = fs.create(path); // 创建文件
              for(String line : contents) { // 写入数据
                   output.write(line.getBytes("UTF-8"));
                   output.flush();
              }
         } catch (IOException e) {
              e.printStackTrace();
         } finally {
              try {
                   output.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
         }
    }
    

    客户端写数据内部实现原理

    打开一个DFSOutputStream流,Client会写数据到流内部的一个缓冲区中,然后数据被分解成多个Packet,每个Packet大小为64k字节,每个Packet又由一组chunk和这组chunk对应的checksum数据组成,默认chunk大小为512字节,每个checksum是对512字节数据计算的校验和数据。
    image

    创建Packet

    当长度满足一个Chunk大小(512B)时,便会创建一个Packet对象,然后向该Packet对象中写Chunk Checksum校验和数据,以及实际数据块Chunk Data,每次满足一个Chunk大小时,都会向Packet中写上述数据内容,直到达到一个Packet对象大小(64K),就会将该Packet对象放入到dataQueue队列中,等待DataStreamer线程取出并发送到DataNode节点。

    发送Packet

    DataStreamer线程从dataQueue队列中取出Packet对象,放到ackQueue队列中,然后向DataNode节点发送这个Packet对象所对应的数据。

    接收ack

    发送一个Packet数据包以后,会有一个用来接收ack的ResponseProcessor线程,如果收到成功的ack,则表示一个Packet发送成功。如果成功,则ResponseProcessor线程会将ackQueue队列中对应的Packet删除。如果发生错误,则重新加入dataQueue。

    DataNode写数据内部原理

    DataNode接受到数据Packet,首先将数据写道下一个DataNode的PipeLine中,然后再写入本地磁盘的Block中,写完后回复上一个DataNode ack。整个Block写成功后,通知NameNode已经收到Block。实际流程如图所示:
    image

    持久化包括block的持久化和block checksum的持久化,
    image

    HDFS读文件

    HDFS读文件分为两步:从NameNode读取文件Block列表,从DataNode读取Block。下面是一段读文件的代码

    package org.shirdrn.hadoop.hdfs;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FSDataInputStream;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    
    public class HdfsFileReader {
    
         public static void main(String[] args) {
              String file = "hdfs://hadoop-cluster-m:8020/data/logs/basis_user_behavior/201405071237_10_10_1_73.log";
              Path path = new Path(file);
             
              Configuration conf = new Configuration();
              FileSystem fs;
              FSDataInputStream in;
              BufferedReader reader = null;
              try {
                   fs = FileSystem.get(conf);
                   in = fs.open(path); // 打开文件path,返回一个FSDataInputStream流对象
                   reader = new BufferedReader(new InputStreamReader(in));
                   String line = null;
                   while((line = reader.readLine()) != null) { // 读取文件行内容
                        System.out.println("Record: " + line);
                   }
              } catch (IOException e) {
                   e.printStackTrace();
              } finally {
                   try {
                        if(reader != null) reader.close();
                   } catch (IOException e) {
                        e.printStackTrace();
                   }
              }
         }
    
    }
    

    读取文件流程:

    1. 客户端调用open打开文件,然后从NameNode获取Block信息(默认预先获取10个),文件长度等信息。
    2. 获取Block列表后,会对Block列表中的DataNode进行排序,排序规则为以下两点
      1. Client到Block所在的Datanode的距离
      2. 如果某个DataNode很久没有心跳,那么就放在列表的最后
    3. 开始对Block进行读取,通过偏移量获取Block,然后根据Block选择合适的DataNode,从DataNode读取数据。重复3,直到读完数据。
    4. 如果客户端没有缓存需要的Block,又向NameNode拉取Block。跳2

    注:有个博客说一旦写入的数据超过一个块的数据,新的读取者就能看见第一个块。对于之后的块也是这样。总之,它始终是当前正在被写入的块,其他读取者是看不见它的。

    一致性模型

    根据上面的写入操作,我们知道在写完了一个Block后,客户端会调用fsync向NameNode提交Block完成信息。所以:

    1. 对于一个完整的Block而言,是强一致性的。
    2. 对于不足一个Block的数据而言,数据可能因为HDFS宕机而丢失。这种情况用户主动调用fsyc向NameNode持久化

    容错

    HDFS通过复制副本实现容错,NameNode来控制所有的block的复制决策。一般而言,3副本配置是同一机架不同机器放置2个,不同机架放置1个。
    image

    DataNode容错

    通过心跳机制检测DataNode,如果一段时间没有收到DataNode的心跳信息,判定机器宕机,设置这台机器不可用。因此Namenode会检测是否有文件block的副本数目小于设置值,如果小于就自动开始复制新的副本并分发到其他Datanode节点。

    如果在写入的时候,发现某台DataNode一直不回应,向NameNode重新申请DataNode。

    检查文件的块的完整性

    客户端读取文件后,会检查和文件关联checksum是否一致,如果不一致,从其它DataNode读取数据

    NameNode容错

    NameNode磁盘中会有元数据信息的持久化信息,可以根据磁盘数据恢复元数据信息。万一NameNode磁盘也损坏了,Secondary NameNode还有历史信息。

    集群均衡

    image

    1. 数据均衡服务(Rebalancing Server)首先要求 NameNode 生成 DataNode 数据分布分析报告,获取每个DataNode磁盘使用情况
    2. Rebalancing Server汇总需要移动的数据分布情况,计算具体数据块迁移路线图。数据块迁移路线图,确保网络内最短路径
    3. 开始数据块迁移任务,Proxy Source Data Node复制一块需要移动数据块
    4. 将复制的数据块复制到目标DataNode上
    5. 删除原始数据块
    6. 目标DataNode向Proxy Source Data Node确认该数据块迁移完成
    7. Proxy Source Data Node向Rebalancing Server确认本次数据块迁移完成。然后继续执行这个过程,直至集群达到数据均衡标准

    第2步中,HDFS会把当前的DataNode节点,根据阈值的设定情况划分到Over、Above、Below、Under四个组中。在移动数据块的时候,Over组、Above组中的块向Below组、Under组移动。
    image

    发展与改进

    NameNode的HA方案

    需要有如下保证

    1. 共享第三方存储,NameNode强一致性
    2. 需要隔离措施保证不会出现脑裂

    NameNode联盟

    单点NameNode联盟成为HDFS的瓶颈,主要表现在:限制了文件的个数;都与NameNode交互性能瓶颈。提出NameNode联盟解决这个问题

  • 相关阅读:
    DevExpress GridControl使用方法
    DevExpress中,添加Winform窗体到DockPanel z
    取消默认 $ 定义
    五角星效果实现
    jquery的each函数的用法
    Object类型转换为long或者Long
    easyui datagrid 列排序
    redis.clients.jedis.exceptions.JedisDataException: WRONGTYPE Operation against a key holding the wrong kind of value
    DIV元素不换行
    JS 中div内容的显示和隐藏
  • 原文地址:https://www.cnblogs.com/biterror/p/6909920.html
Copyright © 2020-2023  润新知