1. 客户端读取HDFS文件的流程
1.1 具体的流程
-
初始化
FileSystem
,client调用FileSystem
对象的open()
方法,打开一个HDFS文件。实际,FileSystem
对象是一个DistributedFileSystem
实例 -
DistributedFileSystem
通过RPC调用NameNode,获取一批文件block的位置列表。其中,每个block的副本所在的DataNode,是按照它们与客户端的距离排序的 -
DistributedFileSystem
向客户端返回一个FSDataInputStream
对象(open()
方法的返回结果),用于数据的读取。FSDataInputStream
中包含了DFSInputStream
对象,它管理着DataNode和NameNode的IO以上对应图中的步骤1和2
-
客户端调用
FSDataInputStream
对象的read()
方法,实际:存有block位置的DFSInputStream
会连接最近的DataNode,对该DataNode反复执行read()
,从而将数据从DataNode传到client。①
DFSInputStream
发现DataNode故障,会记住该DataNode并从最近的另一个DataNode读取副本,以后保证不会反复从该DataNode读取block
②DFSInputStream
会对读取到的数据做checksum,如果发现数据损坏,会从其他DataNode读取副本并向NameNode通知损坏的block信息 -
DFSInputStream
完成block的读取,会关闭与该DataNode的连接。接着,创建与下一个block的最佳DataNode的连接,继续进行block的读取。 -
DFSInputStream
中存储的block位置可能是文件的部分block,它会按照需要从NameNode获取下一批block的位置信息。以上对应步骤3、4和5
-
client读取数据完毕,则调用
FSDataInputStream
对象的close()
方法关闭输入流
1.2 架构优势
- client从NameNode获取block的位置(多副本,排序后的DataNode地址),然后直接与DataNode连接读取数据,这样的设计允许HDFS扩展到大量的并发client。
- NameNode负责响应获取block位置的请求,而不负责响应数据请求,极大减轻了NameNode的压力。避免客户端增长后,NameNode称为系统瓶颈。
1.3 Hadoop对节点距离的描述
- Hadoop集群的节点,可能位于同一机架、同一机房不同机架、不同机房
- HDFS读写文件block时,client只有读取最近的DataNode才能提高数据读写的速度
- 因此,如何定义节点间的距离非常重要 —— 使用带宽描述两个节点之间的距离
- 针对以下场景,带宽将依次递减:
① 同一节点上的两个进程
② 同一机架上的不同节点
③ 同一数据中心(也就是机房)的不同机架
④ 不同数据中心 - 两个节点间的距离,是它们到最近共同祖先的距离之和
- 因此,图示中的节点距离就非常清楚了:
① 节点1上的两个进程的距离为0
② 节点1和节点2的距离为2
③ 节点1和节点3的距离为4
④ 节点1和节点4的距离为6
- 注意:
Hadoop无法自动发现网络拓扑结构,需要进行手动配置。一般情况下,默认集群同一数据中心的同一机架上
2. HDFS数据写入流程
2.1 HDFS中block、packet、chunk
- 之前有说过,HDFS中数据存储单位是block,从Hadoop 2.7.3开始,block的默认大小从
64MB
变成128MB
- client向DataNode、以及DataNode的pipeline之间,数据传输的第二大单位是packet,为
64 kb
- client向DataNode、以及DataNode的pipeline之间,数据校验的基本单位是chunk,默认为
512 byte
- 每个chunk通过校验后,会带上
4 byte
的校验信息。因此,实际写入packet的chunk大小为516 byte
2.2 client的数据三层缓存
- 上传的文件,是按block进行切分的。例如,一个300MB的文件,将会被切分成128MB、128Mb、44MB三个块
写过程会涉及chunk、packet、DataQueue/AckQueue这样的三层缓存:
- 数据流入
DFSOutputStream
时,会先写入一个chunk大小的缓冲区。当数据写满一个chunk时,或遇到强制的flush()操作时,会计算校验和(checksum) - 通过数据校验的chunk会加上校验和,一起写入packet中。当packet被chunk填满时,packet会发送到DataQueue中
DataStreamer
负责将DataQueue中的packet发送到最佳的DataNode中。同时,由于packet此时未确认写入成功,因此会被移动到AckQueue中等待写入确认。- 当收到足够的确认应答(ack)后,
ResponseProcessor
会将packet从AckQueue中移除;否则, 会将其恢复到DataQueue的最前端,以保证没有packet丢失。因此,写入成功的packet在DataQueue和AckQueue中,应该都是不存在的。
2.3 HDFS的写过程
-
初始化
FileSystem
对象,实际是一个DistributedFileSystem
实例。 -
clinet调用
DistributedFileSystem
的create()
方法,创建一个HDFS文件。DistributedFileSystem
会通过RPC调用NameNode,进行文件创建的检查:client的权限检查、是否已存在该文件等。 -
通过检查后,NameNode会向EditLog写入一条新建文件的记录(WAL),然后
DistributedFileSystem
会向client返回一个FSDataOutputStream
对象;否则,文件创建失败并向client抛出IOException
异常 -
FSDataOutputStream
对象中包含了DFSOutputStream
对象,DFSOutputStream
负责管理DataNode和NameNode的IO对应图中的步骤1、2
-
client调用
FSDataOutputStream
对象的write()方法向DataNode写入数据。实际,DFSOutputStream
会将数据划分成一个个packet,先将packet存如DataQueue中。 -
由
DataStreamer
负责管理DataQueue,它挑选出一组适合存储副本的DataNode,并以此来要求NameNode分配适合新的block。这组DataNode之间会形成pipeline,packet通过pipeline进行传输DataStreamer
将packet从DataQueue发送至第一个DataNode- 第一个DataNode将packet发送给第二个DataNode
- 第二个DataNode将packet发送给第三个DataNode,从而实现packet的三副本存储
-
同时,
DataStreamer
还会将未确认成功的packet移入AckQueue中,只有收到三个DataNode的ack packet后,ResponseProcessor
才会将packet从AckQueue中移除对应步骤3、4、5
-
如果写入期间某个DataNode故障:
- 首先会关闭pipeline,将AckQueue中所有packet都放回到DataQueue中了,以保证故障DataNode的下游DataNode不会出现数据丢失;
- 存储在正常DataNode中的block会被做标记,并发送给NameNode,以便故障DataNode恢复时能删除不完整的block
- 在剩下的两个DataNode中重新建立pipeline,余下的数据会继续写入正常的DataNode。
- NameNode在观察到副本数不够时,会在另一个DataNode上创建新的副本
- block成功写入的副本数满足最小副本数(
dfs.namenode.replication.min
,默认为1),则认为写入成功。后续通过异步的副本复制,来达到副本数要求。
故障情况的处理
-
client完成数据写入,调用
DistributedFileSystem
的close()
方法,关闭数据流 -
DistributedFileSystem
会调用通知NameNode文件写入完成前,等待确认。实际,只需要等待block满足最小副本数,就可以确认写入成功。对应图中的步骤6、7
3. 其他知识
3.1 关于副本放置策略
- HDFS在进行写入时,会尽量不在一个机架放置过多的副本
- 以三副本为例:
① 如果client本身就是一个DataNode,则将副本1写入本地;否则,在集群中随机选择一个节点。都记为 r 1 / d 1 r1/d1 r1/d1
② 第二个副本放置在随机选择的、不同机架的DataNode上,记为 r 2 / d 2 r2/d2 r2/d2
③ 第三个副本放置在上一副本相同的机架、随机选择的DataNode上,记为 r 2 / d 3 r2/d3 r2/d3
3.2 关于hflush()
- 文件系统的一致性模型:文件读/写数据的可见性
- HDFS为了性能,牺牲了一些POSIX要求:当前正在写入的block,对其他reader不可见。
HDFS提供了将缓存中的数据刷新到DataNode的方法:
hflush()
:
① 在Hadoop 1.x
中叫做sync()
,可以保证将数据写入DataNode的内存,但不保证数据写入磁盘 —— 存在断电数据丢失的风险
② 保证文件中到目前为止写入的数据均到达所有DataNode的pipeline中,并且对所有新的reader可见 (其实,自己并不是很理解
😂 )hsync()
:可以保证将数据写入到DataNode的磁盘,在Hadoop 1.x
中未提供hflush()
和hsysnc()
的使用都会带来额外的开销,如果想要数据写入的稳定性,需要以适合的频率调用hflush()
或hsysnc()
方法
3.3 关于distcp
- Hadoop中自带一个应用程序
distcp
,可以将大量文件复制到Hadoop中,或者从Hadoop3文件系统中复制大量文件 - 是
hadoop fs -cp
的替代,可以实现文件/目录的复制 - 实质: distcp是一个MR作业,只有map任务,没有reduce任务
- 一般map任务的个数为20,但可以通过
-m
参数进行指定。 - 如果指定一个map任务,则文件的所有block的第一个副本都会存储到map对应DataNode上
4. 总结
-
HDFS的读流程:
- open()方法 + get block locations,获取block的副本位置,返回
FSDataInputStream
,内含DFSDataStream
- 调用
FSDataInputStream
的read()
方法,实际由DFSDataStream
与最佳DataNode创建连接,不断调用read()
读取数据; - 结束一个block的读取,
DFSDataStream
与下一个block的最佳DataNode连接连接,继续进行数据读取 - 必要时,
DFSDataStream
从NameNode处获取下一批block的位置 - 调用
FSDataInputStream
的close()
方法,关闭输入流
- open()方法 + get block locations,获取block的副本位置,返回
-
HDFS读流程中的故障:
- DataNode故障,记住该DataNode,切换到最邻近的、有相同副本的DataNode
- 读数据时进行checksum,发现block损坏,切换到另一副本继续读。同时向NameNode上报损坏的block
-
HDFS的三种数据块:数据存储单位block(
128 MB
)、数据传输单位packet(64 kb
)、数据校验单位chunk(512 byte
+4 byte的校验信息
) -
HDFS基于chunk、packet、DataQueue/AckQueue的三层缓存
- chunk写满,校验后写入packet
- packet写满,放到DataQueue
DataStreamer
将DataQueue中的packet发送给DataNode,并在未确认写入ok时,将packet存入AckQueue- 收到足够的应答,
ResponseProcessor
将packet从AckQueue移除;否则,移回DataQueue中,重新写入
-
HDFS的写流程
- create() + create, NameNode进行权限、是否已存在等校验;通过校验,向EditLog新增一条记录(WAL)并返回
FSDataOutputStream
,内含DFSOutputStream
;否则,抛出IOException
异常 - 调用
FSDataOutputStream
的write()
方法,实际由DFSOutputStream
将数据分成一个个packet放入DataQueue DataStreamer
负责选取一组DataNode进行数据写入,这些DataNode之间会建立pipeline,packet通过pipeline进行传输- 为确认写入ok的packet放入AckQueue,只有收到足够的ack packet,才会从AckQueue中移除
- 调用
FSDataOutputStream
的close()
方法,关闭写入流;NameNode执行complete,等待文件写入完成前,确认成功写入
- create() + create, NameNode进行权限、是否已存在等校验;通过校验,向EditLog新增一条记录(WAL)并返回
-
HDFS写流程故障:
- 删除pipeline、将AckQueue中的所有packet移动到DataQueue的前端,确保数据不会丢失
- 标记当前block,并向NameNode上报信息,等到故障DataNode恢复,删除不完整的block
- 在正常的DataNode之间重新建立pipeline,继续进行数据写入
- NameNode发现副本数不足,在其他DataNode创建新的副本(异步副本复制?)
- 写入成功:只要满足最小副本数,即可认为写入成功
-
副本的放置策略:随机选择,尽量不在一个机架放置过多的副本
-
hflush()
和hsync()
方法:以适合的频率调用,保证写入数据对新的reader的可见性 -
ditscp
:实质是只有map任务的MR作业,可以通过-m
指定map任务数