Hadoop之HDFS理解
理论知识点
存储模型
- 文件线性按字节切割成block,具有offset,id(所有的文件都可以看作字节数组)
- 文件与文件的block大小可以不一样
- 一个文件除了最后一个block,其他的block大小一致
- block的大小根据硬件的I/O特性调整(1.X默认是64M,2.X默认是128M)
- block被分散存放到集群的节点中,具有location
- block具有副本,没有主从概念,副本不能出现在同一节点(满足可靠性)
- 文件上传可以指定block大小和副本数量,上传后只能修改副本数量
- 不支持修改,支持追加数据
架构设计
- HDFS是一个主从架构
- 由一个NameNode和一些DateNode组成
- 面向文件包含:文件数据(data)和文件元数据(metadata)
- NameNode负责存储和管理元数据,并维护了一个层次型的文件目录树(不是物理的磁盘)
- DataNode负责存储文件数据,其中文件数据包括原始数据和数据的校验和。
- DateNode和NameNode维持心跳(默认是3秒),并汇报持有的block信息
- clint和NameNode交互文件元数据和DateNode交互文件的block数据
角色功能
角色即jvm进程
NameNode
- 完全基于内存存储文件元数据、目录结构、文件block映射
- 需要提供持久化方案保证数据的可靠性
所有基于内存存储的,都需要提供持久化方案。分两种,其一就是记录变换,记录每一步都干了什么,在HDFS中称之为Editlog,完整性比较好,加载恢复数据慢且占空间;第二种则是快照,也就是把当前时间点的内存数据记录下来,HDFS称之为FsImage,恢复速度快但是因为是间隔的,容易丢失数据。 - 提供副本放置策咯
DataNode
- 基于本地磁盘存储block(文件的形式)
- 保存block的校验和数据保证block可靠性
- 与NameNode保持心跳,汇报block列表状态
元数据持久化
在持久化的时候,文件属性会持久化,但是文件的每一个block不会持久化。因为如果启动的时候有的DateNode节点可能宕机,造成clint读取失败。
- 任何堆文件系统元数据产生修改的操作,NameNode都会使用一种称为EditLog的事务日志记录下来
- 使用FsImage存储内存所有的元数据状态
- 使用本地磁盘保存EditLog和FsImage
- EditLog具有完整性,数据丢失少,但是恢复速度慢、并有体积膨胀的风险
- FsImage具有恢复速度快,体积与内存数据相当,但不能实时保存,数据丢失多
- NameNode使用了FsImage+EditLog整合的方案
例如:系统当前磁盘里记录了8点钟的快照(FsImage)也记录了8-9点钟的各种操作(Editlog),那么HDFS想要在9点钟持久化,只需要在8点钟的FsImage上进行增量操作(Editlog),写回磁盘里。
安全模式
- HDFS搭建时会格式化,格式化操作会产生一个空的FsImage
- 当NmaeNode启动时,它从硬盘钟读取EditLog和FsImage
- 将所有EditLog中的事务作用在内存中的FsImage上
- 并将这个新版本的FsImage从内存中保存到本地磁盘上
- 然后删除旧的EditLog,因为这个旧的EditLog的事务都已经作用在FsImage上了
- NmaeNode启动后会进入安全模式,在安全模式中,NameNode不会进行数据块的复制。
- NameNode从所有的DateNode接收心跳信号和块状态报告,每当NameNode检测确认某个数据块的副本数达到最小值,那么该数据块会被认为是安全的。
- 在一定百分比的数据块被NameNode检测确认安全后,加上额外的30秒,NameNode将退出安全模式
- 剩下没确定的数据块的副本没有达到指定数目,会复制到其他DateNode上
副本放置策略
- 第一个副本:放置在上传文件的DataNode;如果是集群外提交,则随机挑选一台磁盘不太满,cpu不太忙的节点
- 第二个副本:放置在与第一个副本不同的机架节点上
- 第三个副本:放置在与第二个副本相同的机架节点上
- 更多的副本:随机节点
读写流程
写流程
- 1.Client与NameNode进行通信,NameNode创建文件的元数据,并判断元数据是否有效(文件是否存在,权限),写入EditLog。
- 2.Client会对文件分割成块(block),然后Client向NameNode请求上传第一个块
- 2.NameNode会向Client提供副本放置策略,返回一个有序的DateNode列表
- 3.Client与最近的DateNode进行连接(pipeline),并将块切分成packet(64KB),使用chunk(512B)+ checksum(4B)填充,每当满64KB,就会向第一个DateNode发送
- 4.第一个DateNode收到packet后,本地保存并发送到第二个DateNode,第二个DateNode收到packet后本地保存并发送给第三个DateNode,每个DateNode收到packet都会检验数据,并向前一个DateNode发送ack。这一过程中,Client会同时给第一个DateNode发送packet。
- 5.当整个块传输完成后,DateNode会向NameNode汇报。同时Client传输下一个块。
读流程
- 1.Client向NameNode请求下载文件,即向NameNode通信查询元数据(block所在的DateNode节点),找到文件块所在的DateNode服务器
- 2.Client尝试下载block,并校验数据完整性
HDFS中的高可用
单点故障
- 高可用方案:HA(High Availiable)
- 多个NameNode,主备切换
压力过大,内存受限
- 联邦机制:Federation(元数据分片)
- 多个NameNode,管理不同的元数据
HA
要实现hadoop的高可用,需要满足:
- 主备切换对于用户来说是透明的
- 保证主备的数据一致
- 解决脑裂问题
NameNode主备切换
Hadoop借助Zookeeper来实现,首先active状态和standby状态的NameNode都会有各自的zkfc,zkfc是一个在NameNode中的进程,时时监控这NameNode的状态。
- 1.在NameNode启动时,两个zkfc会创建目录 也就是抢锁的过程,谁创建目录成功谁是active
- 2.如果active挂掉了,zkfc会知道,会把锁删除,触发standby的callback函数,继而上锁。
- 3.standby的zkfc会查看active的NameNode是否真正的挂掉了,如果真正挂了会切换状态为active
保证主备数据一致
运用QJM,基于Paxos算法(解决分布式环境中数据如何达成一致)
- 1.主备NameNode中间会有2N+1个JN(JournalNode),每次NameNode写editlog只要其中N+1以及以上的JN返回成功,NameNode就认为写入成功
- 2.standby状态的NameNode会定时从JN中读取一批editlog,并作用于内存中的Fsimage中,通知active,avtive的NameNode过来取。
- 3.DaateNode会和active、standby保持心跳。
脑裂问题
Hadoop中脑裂问题有两方面
- 第一种是共享存储中的脑裂问题,确保只有一个NameNode可以写成功
NameNode每次写Editlog都需要传递一个编号Epoch给JN,JN会对比Epoch,如果比自己保存的Epoch大或相同,则可以写,JN更新自己的Epoch到最新,否则拒绝操作。在切换时,Standby转换为Active时,会把Epoch+1,这样就防止即使之前的NameNode向JN写日志,也会失败。 - 第二种是DataNode的脑裂问题,确保只有一个NameNode能命令DataNode。
每个NameNode改变状态的时候,向DataNode发送自己的状态和一个序列号。并维护这个序列号,当NameNode再次改变时,新的NameNode接收到DataNode心跳时会返回自己的active状态和一个更大的序列号,当DataNode接收这个序列号时,认为该NameNode是新的active。如果原来的active这时候恢复了,与DataNode链接时发送的还是原来的序列号,小于新的序列号,这时DataNode会拒绝NameNode的命令
联邦机制
- 这些NameNode直接相互独立,各自分工管理自己的区域,且不需要互相协调,一个NameNode挂掉了不会影响其他的NameNode
- DataNode被用作通用的数据存储设备,每个DataNode要向集群中所有的NameNode注册,且周期性的向所有NameNode发送心跳和报告,并执行来自所有NameNode的命令
- 一个Block Pool由属于同一个NameSpace的数据块组成,每个DataNode可能会存储集群中所有Block Pool数据块,Block Pool之间不会交流,各自管理
- NameNode和Block Pool一起被称作NameNode volume,它是管理的基本单位,当一个NameNode被删除后,所有DataNode上与其对应的Block Pool也会被删除。当集群升级时,每个NameNode volume作为一个基本单元进行升级