HDFS的数据通信机制
HDFS中很有意思的一点是,它的控制消息的传输和数据消息的传输采用的是不一样的模块。这也是接下来我想探讨的重点。
HDFS中所有的控制消息的传输都基于它自实现的RPC模块,之前已经讨论过。
但是,Hadoop自己的RPC机制不太适合大数据量的传输,因为两个Node之间只用一个socket进行通信,网络的吞吐量不一定上得去。
而事实上,HDFS确实没有用RPC机制传输数据消息。当HDFS中的DFSClient对DataNode上保存的文件数据进行读写的时候,它其实采用了另外一个机制.
简单而言,HDFS分为了三个部分:
NameNode,处于master的地位,维护了数据在DataNode上的分布情况,并且,还要负责一些调度任务;
DataNode,存储真实数据的地方;
DFSClient,一个client端,通过它提供的接口访问NameNode和DataNode;
三者之间的通信全部是基于TCP/Socket; 如图所示:
图中,连线表示两者之间存在通信,箭头一方表示接收请求,没有箭头的一端表示发起请求的一方;图中的黑色细线表示控制消息的通路,红色粗线表示数据消息的通路;
可以看得出来,NameNode是一个典型的Server端程序,它总是处于接受请求,返回响应的状态中。NameNode永远不会主动的向其它组件发起请求(依稀记得GFS的论文中也是这样做的)。如果NameNode需要向DataNode发送一些调度或者控制命令的话,必须等待DataNode向NameNode发送heartbeat之后,作为heartbeat的response返回给DataNode。
DataNode就比较忙了,它不仅需要定时的发送heartbeat给NameNode,并且heartbeat的返回往往还附带了很多的控制消息需要处理,同时,DataNode要接收DFSClient来的读写数据的请求和一些控制请求,最后,DataNode之间还有数据消息和控制消息的传输。
每个DataNode在启动的时候会创建一个线程DataXceiverServer来专门负责block数据的读写的链接。而DataXceiverServer做的事情很简单 --一旦有一个连接,就创建一个新的DataXceiver来处理这个连接:
public void run() {
while (datanode.shouldRun) {
try {
Socket s = ss.accept();
s.setTcpNoDelay(true);
new Daemon(datanode.threadGroup,
new DataXceiver(s, datanode, this)).start();
} catch (SocketTimeoutException ignored) {
// wake up to see if should continue to run
} catch (IOException ie) {
// ............
} catch (Throwable te) {
// ............
}
}
try {
ss.close();
} catch (IOException ie) {
// .......
}
}
DataXceiver也是一个线程,它负责处理对应的一个连接,主要完成4种任务:
opReadBlock: 读取一个block
opWriteBlock: 写一个block到disk上
opCopyBlock: 读一个block,然后送到指定的目的地
opReplaceBlock: 替换一个blockclass DataXceiver extends DataTransferProtocol.Receiver
implements Runnable, FSConstants {
// ................
/**
* Read/write data from/to the DataXceiveServer.
*/
public void run() {
updateCurrentThreadName("Waiting for operation");
DataInputStream in=null;
try {
in = new DataInputStream(
new BufferedInputStream(NetUtils.getInputStream(s),
SMALL_BUFFER_SIZE));
final DataTransferProtocol.Op op = readOp(in);
// Make sure the xciver count is not exceeded
// ....
processOp(op, in);
} catch (Throwable t) {
LOG.error(datanode.dnRegistration + ":DataXceiver",t);
} finally {
//.....
}
}
/** Process op by the corresponding method. */
protected final void processOp(Op op, DataInputStream in
) throws IOException {
switch(op) {
case READ_BLOCK:
opReadBlock(in);
break;
case WRITE_BLOCK:
opWriteBlock(in);
break;
case REPLACE_BLOCK:
opReplaceBlock(in);
break;
case COPY_BLOCK:
opCopyBlock(in);
break;
case BLOCK_CHECKSUM:
opBlockChecksum(in);
break;
default:
throw new IOException("Unknown op " + op + " in data stream");
}
}