1 HDFS初识
考虑这样一个问题:文件切成很多小文件块散列存储在集群中时,是如何知道每个小文件块存储的位置的呢?让我们举个例子来解释一下,假设现在有100台机器,如果有10个人拿着10批数据过来存储,那么他们可能会找到不同的人把他们的数据各自存储在不同的机器上,过了10天之后,这些人想要取回他们的数据,但是他们忘了自己的数据存储在谁那里了,那么这些数据就取不回来了。那么该如何解决这个问题呢?我们可以单独拿出一台机器用于记录,那么每当有人要存储数据的时候,都先找到这台机器,然后这台机器会告诉他将数据存储在哪,这个询问的过程是很快的,每个人问完之后就会拿着数据去存储,然后下一个人过来接着问...如果这个例子你能听懂,那么恭喜你,HDFS你已经学会了。上述的过程其实就是指的HDFS中的NameNode和DataNode各司其职的过程,DataNode用于存储数据,而NameNode用于存储文件的元数据描述信息。让我们来进一步思考一下,如果把上述例子中的文件数据换成一张一张的一百块钱,比如有个人过来要存100块钱,我说你去张三那存吧,这时候我应该立刻写下谁谁谁的100块钱存在张三那了吗?这是不可能的,因为有可能我记完之后这个人没去张三那存,如果我记下了这笔帐,那个人回来找我取回那100块钱,我让他去张三那,那张三就有苦说不出了,这就是所谓的数据一致性问题。数据的一致性问题一定是要非常小心的,尤其是在分布式、多节点多人协作的情况下。因此在上面的例子中,一定是要张三给我发送一个确认的回报之后,我才能把这个100块钱给记录下来的。HDFS就是这样一个用多个节点散列未来要存储的数据,然后用一个主节点进行记账的分布式文件系统。此外,记账这件事情,一个人可以做,两个人也可以做,但是两个人记账中面临的数据一致性问题会更加复杂(两个人之间同步信息),因此HDFS采用了一主的架构。
分布式文件系统那么多,为什么hadoop项目中还要开发一个hdfs文件系统?
我们知道,在hadoop项目里面除了hdfs文件系统模块之外,还有另外一个很重要的用于计算的模块,因此我们可以推测出,hdfs一定会具备一个特征:它能更好的支持分布式计算。
2 HDFS理论知识点
- 存储模型
- 架构设计
- 角色功能
- 元数据持久化
- 安全模式
- 副本放置策略
- 读写流程
- 安全策略
2.1 存储模型
- 文件线性按字节切割成块(block),具有offset,id
- offset就是指偏移量,文件的第一个block块偏移量为0,后续block块的偏移量可以通过block块的大小来计算,只要有了offset,就能把所有的block块拼成一个完整的文件。
- id标识了每个block块的名字,用于映射
- 文件与文件的block大小可以不一样
- 一个文件除最后一个block,其他block大小一致
- 比如block大小为4个字节,而最后一个block块的大小可能不够4个字节
- block的大小依据硬件的I/O特性调整
- block被分散存放在集群的节点中,具有location(block块所在节点的位置)
- Block具有副本(replication),没有主从概念,副本不能出现在同一个节点中
- 如果副本放在一个节点中,那么如果这个节点挂掉了,所有副本都读取不到了
- 副本是满足可靠性和性能的关键
- 文件上传可以指定block大小和副本数,上传后只能修改副本数
- 一次写入多次读取,不支持修改
- 比如在文件中增加一些内容,这会导致修改位置所在的block大小变大,这时候会将这个block块中多出来的内容转移到下一个block块中,而这又会导致下一个block块大小变大...这是一个泛洪的操作,这会导致集群中很多的节点,它们的CPU、内存、网卡会参与到因为一个修改的事情而造成的资源的高度使用,网络一直被疯狂的传输数据,因此hdfs设计者做了一个折中的方案,就是hdfs作为一个文件系统,它可以存,可以读,可以批量计算,它可以支持很多程序进行计算,它可以让每个程序都跑得很快,但是它不支持修改。
- 支持追加数据
- 追加数据只是在文件的最后一个block块中添加内容,或者是在文件的最后添加block块,因此是可以支持的。
无论是什么文件,在计算机中存储的都是二进制,二进制在计算机中就是一个个二进制位,但是在计算机中,一般很少用二进制位去描述文件,而是会用字节去描述,我们看到的文件大小一般都是用字节作为单位的,文件本质上都是字节数组。
2.2 架构设计
- HDFS是一个主从(Master/Slaves)架构
- 由一个NameNode(主)和一些DataNode(从)组成
- 面向文件包含:文件数据(data)和文件元数据(metadata)
- NameNode负责存储和管理文件元数据,并维护了一个层次型的文件目录树
- DataNode负责存储文件数据(block块),并提供block的读写
- DataNode与NameNode维持心跳,并汇报自己持有的block信息
- Client和NameNode交互文件元数据、和DataNode交互文件block数据
Windows和Linux文件系统的差异。
Windows和Linux上都会有硬盘,而硬盘上都有分区,在windows上分区对应的是盘符(c盘、d盘),这个时候如果你想要存一些目录,需要自主去找一个分区,在分区下去存一级目录、二级目录,而linux中虽然也有两个分区,但是它的分区会挂载到它内存的虚拟目录树结构上,它是由一个虚拟的根起,根下的A目录可能是你的第一个分区,B目录可能是你的第二个分区。因此在你使用linux系统的时候,你好像感觉不到底层到底分了几个分区,而在使用windows的时候,容易混乱。比如说,如果你在windows中写了一个软件,这个软件必须从G盘加载一个文件(conf、xml),那么这个软件在其他人的电脑上面可能就跑不起来了,因为有的电脑上根本就没有G盘,而如果你使用的是linux系统,linux系统除了它的根目录结构之外,还有它的mount挂载(/g -> disk:G分区、/b -> disk:B分区),上述软件如果是在linux系统上面开发的,假如它的文件加载路径还是写死的,比如从/g目录下加载文件,这时候其他电脑可能也没有/g目录,但是这时它可以创建一个/g目录(可以挂载在不同的分区上,它的映射关系可以随便换),因此linux相对于windows来说使得软件具备了移动性。
2.3 角色功能
NameNode
- 完全基于内存存储文件元数据、目录结构、文件block的映射
- 需要持久化方案保证数据可靠性
- 提供副本放置策略
DataNode - 基于本地磁盘存储block(文件的形式)
- 并保存block的校验和数据保证block的可靠性
- 与NameNode保持心跳,汇报block列表状态
角色即进程。
HDFS并没有真正存储数据,只是管理映射。
2.4 元数据持久化
- 任何对文件系统元数据产生修改的操作,Namenode都会使用一种称为EditsLog的事务日志记录下来
- 使用FsImage存储内存所有的元数据状态
- 使用本地磁盘保存EditsLog和FsImage
- EditsLog具有完整性,数据丢失少,但恢复速度慢,并有体积膨胀风险
- FsImage具有恢复速度快,体积与内存数据相当,但不能实时保存,数据丢失多
- NameNode使用了FsImage+EditsLog整合的方案:
- 滚动将增量的EditsLog更新到FsImage,以保证更近时点的FsImage和更小的EditsLog体积
数据持久化方式
- 日志文件(文本文件):记录(append)实时发生的增删改操作(mkdir /abc),通过读取日志文件重放每一行指令来恢复数据
优点:完整性比较好
缺点:加载恢复数据慢、占空间- 镜像、快照、dump、db(二进制文件):间隔的(小时,天,10分钟,1分钟,5秒钟),内存全量数据基于某一时间点做的向磁盘的溢写
优点:恢复速度快过日志文件
缺点:因为是间隔的,容易丢失一部分数据HDFS:
- EditsLog:日志文件
体积小,记录少:必然有优势- FsImage:镜像、快照
如果能更快的滚动更新时点,必然有优势HDFS采用的是最近时点的FsImage + 增量的EditsLog的持久化方案。
比如现在10点,HDFS中记录了9点的FsImage + 9点到10点的增量的EditsLog,那么通过以下步骤就能得到关机前的全量数据:
- 加载FsImage
- 加载EditsLog
FsImage时点是怎么滚动更新的?
- 由NameNode8点溢写一次,9点溢写一次(每一小时溢写一次,产生大量I/O,耗费资源)
- NameNode第一次开机的时候只写一次FsImage(假设8点),到9点的时候,EditsLog记录的是8
~
9点的日志,只需要将8~
9点的日志的记录,更新到8点的FsImage中(使用另外一台机器:Secondary NameNode来做合并),FsImage的数据时点就变成了9点。
2.5 安全模式
- HDFS搭建时会格式化,格式化操作会产生一个空的FsImage
- 当NameNode启动时,它从硬盘中读取EditsLog和FsImage
- 将所有EditsLog中的事务作用在内存中的FsImage上
- 并将这个新版本的FsImage从内存中保存到本地磁盘上
- 然后删除旧的EditsLog,因为这个旧的EditsLog的事务都已经作用在FsImage上了
- NameNode启动后会进入一个称为安全模式的特殊状态。
- 处于安全模式的NameNode是不会进行数据块的复制的。
- NameNode从所有的 DatNnode接收心跳信号和块状态报告。
- 每当NameNode检测确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全(safely replicated)的。
- 在一定百分比(这个参数可配置)的数据块被NameNode检测确认是安全之后(加上一个额外的30秒等待时间),NameNode将退出安全模式状态。
- 接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他DataNode上。
NameNode存的元数据主要有两种:文件属性、每个块存在哪个DataNode上。
在持久化的时候,文件属性会持久化,但是文件的每一个块不会持久化。恢复的时候,NameNode会丢失块的位置信息。
那么为什么NameNode不持久化块的位置信息呢?
这就又回到了分布式时代数据一致性的问题了,假如NameNode持久化了块的位置信息,但是由于集群启动时DataNode挂掉了,那么这些块就取不回来了,这就会产生数据不一致的问题,因此NameNode宁可不存块的位置信息,而是等DataNode和NameNode建立心跳,然后向它汇报块的信息,这个过程就叫安全模式。
2.6 HDFS中的SNN
Secondary NameNode(SNN)
- 在非Ha模式下,SNN一般是独立的节点,周期完成对NN的EditsLog向FsImage合并,减少EditsLog大小,减少NN启动时间
- 根据配置文件设置的时间间隔fs.checkpoint.period 默认3600秒
- 根据配置文件设置edits log大小 fs.checkpoint.size 规定edits文件的最大值默认是64MB
SNN存在的意义就是让EditsLog很小、恢复很快。
2.7 Block的副本放置策略
- 第一个副本:放置在上传文件的DN;如果是集群外提交,则随机挑选一台磁盘不太满,CPU不太忙的节点。
- 第二个副本:放置在与第一个副本不同的机架的节点上。
- 第三个副本:与第二个副本相同机架的节点。
- 更多副本:随机节点。
在早期的HDFS中,第一个副本和第二个副本会放在同机架,第三个副本才会出机架,这样当副本数设定为2的时候,如果副本所在机架挂了,会导致这个块丢失,因此在2.x的时候修正了这个问题,第二个副本放在与第一个副本不同的机架上。
第二个副本和第三个副本放在同一机架是为了减少跨交换机的成本。
2.8 HDFS读写流程
2.8.1 HDFS写流程
- Client和NN连接创建文件元数据
- NN判定元数据是否有效
- NN触发副本放置策略,返回一个有序的DN列表
- Client和DN建立Pipeline连接
- Client将块切分成packet(64KB),并使用chunk(512B)+chucksum(4B)填充
- Client将packet放入发送队列dataqueue中,并向第一个DN发送
- 第一个DN收到packet后本地保存并发送给第二个DN
- 第二个DN收到packet后本地保存并发送给第三个DN
- 这一个过程中,上游节点同时发送下一个packet
- 生活中类比工厂的流水线:结论:流式其实也是变种的并行计算
- Hdfs使用这种传输方式,副本数对于client是透明的
- 当block传输完成,DN们各自向NN汇报,同时client继续传输下一个block
- 所以,client的传输和block的汇报也是并行的
Client在与NameNode进行交互的时候,会触发副本放置策略,NameNode会根据副本放置策略,在返回DataNode信息时做一个排序(根据距离),Client本机上的DataNode会排在第一位。然后Client会和第一个DataNode建立tcp连接,第一个DataNode和第二个DataNode建立tcp连接,第二个DataNode和第三个DataNode建立tcp连接,这些连接链路被称为pipline。
DataNode如果挂掉会怎么办?
- 如果是第三个DataNode挂掉了,影响最小,因为前面两个DataNode的pipline连接没有断,继续传输packet即可。
- 如果是第二个DataNode挂掉了,那么第一个DataNode直接与第三个DataNode建立连接,然后根据第三个DataNode已经接收的packet来向它传输下一个packet即可。
- 如果是第一个DataNode挂掉了,那么Client直接与第二个DataNode建立连接,然后把剩余的packet传输给第二个DataNode即可。
DataNode会向NameNode汇报状态,因此在1个DataNode挂掉之后,由于汇报时的DataNode少了一个,这时NameNode会让某个DataNode从自身再复制一个DataNode出来。
2.8.2 HDFS读流程
- 为了降低整体的带宽消耗和读取延时,HDFS会尽量让读取程序读取离它最近的副本。
- 如果在读取程序的同一个机架上有一个副本,那么就读取该副本。
- 如果一个HDFS集群跨越多个数据中心,那么客户端也将首先读本地数据中心的副本。
- 语义:下载一个文件:
- Client和NN交互文件元数据获取fileBlockLocation
- NN会按距离策略排序返回
- Client尝试下载block并校验数据完整性
- 语义:下载一个文件其实是获取文件的所有的block元数据,那么子集获取某些block也应该成立
- Hdfs支持client给出文件的offset自定义连接哪些block的DN,自定义获取数据
- 这个是支持计算层的分治、并行计算的核心