块(block)
- 在 hdfs-site.xml 中通过 dfs.blocksize 参数调整 block 大小,默认为 134217728 B,即 128 M,如果文件比较大,可以调整为 256 M
- HDFS 适用于大文件存储,小文件对于 HDFS 来说是致命的
- 每个公司对于小文件的阈值定义都不同,在这里我们定义 <= 30M 的文件即是小文件(在NN中大概占 250 B),假设 NN 内存为 4G,最多可以放 4294967296 B / 250 B,约为 1700 多万的小文件
- 小文件的危害:1. 撑爆 NN 2. 使用 hive / spark 等进行计算时,会拖慢速度
- 在 hdfs-site.xml 中通过 dfs.replication 参数调整 副本数,默认为 3,即存储 3 份,如果磁盘空间比较紧张,可以调整为 2 份
- 假设块大小为 128 M,副本数为 3 份,有一个 260 M 的文件,请问占用多少块,实际存储是多少? 占用 3 * 3 = 9 个块,实际存储 128 M * 3 = 780 M
HDFS 架构
- NameNode(NN):主节点,存储和维护元数据,包括文件名称,文件的目录结构,文件属性(权限,创建时间,副本数等),还有文件,block和DN的映射关系,这个映射关系不会持久化存储,而是在集群启动和运行时,DN定期发送block report给NN,NN在内存中动态维护这种映射关系
- DataNode(DN):从节点,存储文件内容,文件内容抽象成block块的形式保存在本地磁盘,维护了block id到DataNode的本地文件映射关系
- SecondaryNameNode(SNN):辅助NameNode管理元数据,主要负责合并fsimage和edits,清空edits。合并策略为时间长短和文件大小(默认为1小时或达到64M)
fsimage和edits的合并过程
- fsimage:存储了一份最完整的元数据信息,内容比较大
edits:元数据操作日志,记录了一段时间元数据信息的变化 - 达到合并的触发条件后(默认为1小时或者edits达到了64M),SecondaryNameNode通知NameNode准备切换edits文件
- NameNode接收到切换edits的通知,所有的操作日志往一个新的edits文件中写入
- SecondaryNameNode获取fsimage和edits两个文件,一次性加载到内存合并为一个新的fsimage
- SecondaryNameNode将新的fsimage发送给NameNode替换掉原来的fsimage
副本放置策略
- 第一个副本:
提交节点如果本身就是DN,自己写一份;
否则为集群外提交,则随机挑选一个不太慢、cpu不太忙的节点上 - 第二个副本:
放置在于第一个副本的不同机架的节点上 - 第三个副本:
与第二个副本相同机架的不同节点上
HDFS读流程
- Client向NameNode发起文件读取请求,来确定请求的文件block所在位置
- NameNode会视情况返回文件的部分或者全部block列表,对于每个block,NameNode都会返回含有该block副本的DataNode地址(列表中根据集群的拓扑结构,按照DataNode与Client的距离排序,越近的排名越靠前,NameNode返回的是包含block的DataNode地址,而不是block的数据)
- Client选取靠前的DataNode来读取block,如果Client本身就是DataNode,将从本地直接获取数据(短路读取特性)
- 当读取完毕列表中的block后(并行读取),若文件读取还没有结束,Client会继续向NameNode请求获取下一批的block列表。全部读取完成后,所有block会合并成一个完整的文件
- 每读取完一个block都会进行checksum验证,如果读取DataNode时出现错误,Client会通知NameNode,然后从下一个拥有该block副本的DataNode继续读取。
HDFS写流程
- Client向NameNode发起文件上传请求,NameNode检查文件路径是否合法,上传用户是否有权限,返回是否可以上传
- Client请求第一个block应该传输到哪些DataNode机器上
- NameNode根据配置文件指定的副本数量(默认3份)和机架感知原理(离Client最近的一台机器存一份,相同路由器下存一份,不同路由器下存一份)进行分配,返回可用的DataNode机器地址,如A B C
- Client请求向三台DataNode中的A上传数据(本质上是一个RPC调用,建立pipeline),A收到请求会调用B,然后B调用C,将整个pipeline建立完成,然后逐级返回Client
- Client开始向A上传第一个block,以packet为单位(默认64K),A收到packet就会传给B,B传给C,A每传一个packet会放入一个应答队列等待应答
- 数据被分割成一个个packet数据包在pipeline上传输,在pipeline反方向上逐个发送ack,最终由pipeline中第一个DataNode节点A将pipeline ack 发送给client
- 第一个block传输完成后,Client再次请求NameNode上传第二个block到服务器,重复上述过程,直到文件上传完毕。