写数据
正常写流程
hdfs写数据的时候 会有客户端(程序员写的代码)请求 NameNode,NameNode会先处理你的上传请求, 看看你这个用户是否有权限上传,如果你有权限上传,查看你上传的路径是否已经存在了,如果存在了,看看你客户端的代码参数是否允许覆盖,如果不允许覆盖,就这样结束了.
如果路径也不存在的话,此时NameNode会发送一个可以上传文件的指令到你client客户端,
然后客户端就开始上传文件,文件有200M,但是文件的形式以block的形式存放到hdfs上,此时会先读取这要上传的文件的第一个块儿(这个块儿大小是客户端指定的),比如说客户端指定的block大小是64M,那么第一次读的时候就从0M开始读到64M. 然后先上传第一个block的内容. 然后NameNode会告诉客户端哪些DataNode是可以帮你存储的.
具体给你分配哪些DataNode取决于机架感知.
然后客户端开始给这个文件分为多个block分别上传到多个DataNode上.假如说需要三个DataNode存这个文件.
传输细节是客户端会先建立block传输通道给DataNode1,然后DataNode1会给DataNode2建立通道,DataNode2会给DataNode3建立通道 .
客户端发数据第一个block先发送到DataNode1, DataNode1会发送DataNode2,DataNode2收到了会发给DataNode3.最后DataNode会向NameNode汇报接收的块儿的信息.
然后客户端会发送数据的第二个block块儿给DataNode,重复上面的步骤,直到文件发送完毕.
1)客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
2)NameNode返回是否可以上传。
3)客户端请求第一个 Block上传到哪几个DataNode服务器上。
4)NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
5)客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
6)dn1、dn2、dn3逐级应答客户端。
7)客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
8)当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)。
另外一个版本,有时间整理一下
一:client 发起文件上传请求,通过 RPC 与 NameNode 建立通讯,NameNode检查目标文件是否已存在,父目录是否存在,返回是否可以上传;
二:client 请求第一个 block 该传输到哪些 DataNode 服务器上;
三:NameNode 根据配置文件中指定的备份数量及副本放置策略进行文件分配,返回可用的 DataNode 的地址,如:A,B,C
四:client 请求 3 台 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 到服务器
异常写流程
客户端每次读取64k的数据,封装成一个packet,封装成功的packet会放入到一个dataQueue队列里面,只要封装到这个队列的数据都是待发送的数据包,在发送的时候会将这个队列中的packet按照顺序依次放入到另外一个ackQueue,紧接着ackQueue队列发送通道的第一个DataNode节点,每个节点收到之后会发送确认消息,如果一个小包在发送后,已经收到了所有的DataNode节点返回的ack确认消息,那么这个packet会在ackQueue中删除.
假如一个packet在发送后,在收到DataNode返回的确认ack消息时候超时了,比如说人家规定了5秒钟之内必须收到所有的ack消息,如果已经超过了5秒了,我只是收到其中部分的DataNode节点消息,没有收到所有的ack消息,这就是超时了,一旦超时这时候传输终止,ackQueue中的packet会回滚到dataQueue里面.
紧接着重新建立通道,剔除坏的DataNode节点,通道建立完成之后继续执行上面的步骤.
除非所有DataNode都坏了,否则都能上传成功.
**
读数据
客户端(Java程序员自己写的代码)请求下载文件先检查下载文件的合法性,如果检查合法,此时NameNode会响应你顺便把你要下载的元数据返回给你,元数据就是这个文件有哪些block,这些block存在哪些DataNode节点里面.
返回之后紧接着客户端会直接根据NameNode返回的元数据去找到对应的第一个DataNode节点发请求去下载东西.
此时有可能第一个DataNode宕机了,那么客户端会向列表的第二个DataNode节点发送请求下载,然后依次下载所有的block ,都下载完了之后会依次怼到FsDataInputStream的输入流里面拼接生成最终的文件.
另外一个版本,有时间整理一下:
一:client 发起文件读取请求通过RPC与NameNode建立通讯,nameNode检查文件位置,来确定请求文件 block 所在的位置
二:NameNode会视情况返回文件的部分或者全部block列表,对于每个block,NameNode 都会返回含有该 block 副本的 DataNode 地址;
三:这些返回的 DN 地址,会按照集群拓扑结构得出 DataNode 与客户端的距离,然后进行排序,排序两个规则:网络拓扑结构中距离 Client 近的排靠前;心跳机制中超时汇报的 DN 状态为 STALE,这样的排靠后;
四:Client 选取排序靠前的 DataNode 来读取 block,如果客户端本身就是DataNode,那么将从本地直接获取数据
五:底层上本质是建立 Socket Stream(FSDataInputStream),重复的调用父类 DataInputStream 的 read 方法,直到这个块上的数据读取完毕;
六:当读完列表的 block 后,若文件读取还没有结束,客户端会继续向NameNode 获取下一批的 block 列表;
七:读取完一个 block 都会进行 checksum 验证,如果读取 DataNode 时出现错误,客户端会通知 NameNode,然后再从下一个拥有该 block 副本的DataNode 继续读。
八:read 方法是并行的读取 block 信息,不是一块一块的读取;NameNode 只是返回Client请求包含块的DataNode地址,并不是返回请求块的数据;最终读取来所有的 block 会合并成一个完整的最终文件。