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联盟解决这个问题

原文地址:https://www.cnblogs.com/biterror/p/6909920.html