有关HDFS写流程的系列文章:
【精】彻底吃透HDFS写流程(1)–BlockConstructionStage
【精】彻底吃透HDFS写流程(2)-- Namenode侧create文件
【精】彻底吃透HDFS写流程(3)-- DataStreamer线程和输出流write方法简要分析
【精】彻底吃透HDFS写流程(4)-- 输出流DFSOutputStream#write方法分析
【精】彻底吃透HDFS写流程(5)-- DataStreamer线程类run方法分析以及如何构建pipeline?
【精】彻底吃透HDFS写流程(6)-- addBlock RPC
【精】彻底吃透HDFS写流程(7)-- 客户端sendPacket以及相关输入输出流的对应关系
【精】彻底吃透HDFS写流程(8)-- 数据真正落盘
回顾一下前情提要:
在DFSClient#create中,调用了DFSOutputStream.newStreamForCreate创建DFSOutputStream输出流对象。newStreamForCreate方法里面做了很多事:
① 调用create RPC申请在Namenode侧创建一个文件,并返回一个HdfsFileStatus对象。
② 把第一步中返回的HdfsFileStatus对象传入DFSOutputStream构造函数中,构造一个DFSOutputStream对象。
③ 启动DFSOutputStream对象中的DataStreamer线程。
上一篇文章中我们讲完了在Namenode侧创建一个文件的元数据信息,并把它加入到文件系统目录树中,然后返回给客户端一个HdfsFileStatus对象,也就是上面的第①步。本文我们来介绍剩下的②③两步。
构造DFSOutputStream对象比较简单:
里面比较重要的一点是初始化了DataStreamer对象,DataStreamer本质上是个线程类,在构造完DFSOutputStream后,外层会调用它的start方法启动这个线程。
其实到这里,我们已经知道使用HDFS客户端API写文件时拿到输出流对象背后的全过程了。大家可以回顾一下,如果回想不起来,可以再看一下前面的系列文章加深印象。
那接下来如何继续分析写文件原理呢?其实有以下两个角度:
① 刚才说了DataStreamer是个线程,然后也start了。那我们看DataStreamer类的run方法不就结了么?
② 还可以通过HDFS API写文件的流程出发,拿到输出流out对象之后,要调用out的write方法写数据。那我们看write方法不也可以么?
接下来就分别从这两个角度出发做简要介绍。
一、DataStreamer#run
DataStreamer的功能:用来向处于pipeline中的datanode发送data packet(data packet其实就是数据)。每一个packet都有一个对应的序列号,当一个块的所有的packet都发送出去了,并且每一个packet都收到了datanode发回来的ack响应(这里的ack不是TCP里的那个ack,是HDFS中的叫法),那么DataStreamer就会关闭这个block。
DataStreamer类内部有两个比较重要的队列,一个是dataQueue,一个是ackQueue。DataStreamer线程从dataQueue中取出待发送的packet,把它发送给pipeline中的第一个datanode,并把这个packet从dataQueue中移除放入到ackQueue中。 DataStreamer里面的内部类ResponseProcessor线程类接收来自datanode的ack响应。接收到一个packet的成功的ack后,ResponseProcessor会把相应的packet从ackQueue中移除。
如果发生错误,所有未完成的packet将被移出ackQueue。通过从原始pipeline中剔除掉坏的datanode,建立一个新的pipeline。然后DataStreamer开始继续从dataQueue中取packet然后发送。
DataStreamer的run方法很长,里面的细节后面会开写的文章详细分析源码。
二、DFSOutputStream#write
如下图所示,可以看到DFSOutputStream类的write方法有两个常用的是继承自FSOutputSummer类。我们以第一个为例,去FSOutputSummer中看看。
FSOutputSummer类的write(byte b[], int off, int len)方法中使用FSOutputSummer#write1方法。直接看write1。
上图中writeChecksumChunks方法内部会去触发FSOutputSummer的子类DFSOutputStream的writeChunk方法。
此时是不是感觉通畅了?知道数据是怎么写入的了吧。
概括一下,就是你在用HDFS API中的输出流write数据时,底层会把你写入的数据以chunk为单位写入到DFSPacket对象的buf中。如果达到DFSPacket对象所允许的最大chunk数或者达到了hdfs block size(128M),则将此DFSPacket对象入队到dataQueue中。DataStreamer会从dataQueue中不断取packet发送到pipeline中的datanode上进行写入,并且有专门的线程处理Datanode写入packet数据成功的响应。
三、TODO
以后的文章都以精短为主,不写特别长篇大论的文章,因为那样会容易让看文章的人中途放弃,不如每篇文章讲一个小点效果好。
限于篇幅,后续会开新文章详细介绍本文中没有深入讲解的两个重要的东西:①DataStreamer#run方法的逻辑。
②DFSOutputStream#writeChunk方法的逻辑。
③Namenode侧选datanode的逻辑
③Datanode侧写入的逻辑(pipeline写入、选盘策略等)