hadoop DataNode实现分析
在前面说hadoop整体实现的时候, 说过DataNode的需要完成的首要任务是K-V存储。
第二个功能是 完成和namenode 通信 ,这个通过IPC 心跳连接实现。此外还有和客户端 其它datanode之前的信息交换。
第 三个功能是 完成和客户端还有其它节点的大规模通信,这个需要直接通过socket 协议实现。
下面开始分析源代码,看看DataNode是如何实现这些功能的。
分析代码采取自定向下的分析方式, 看看代码中做了啥,然后分析这些代码的作用。
首先看Datanode实现的接口。
- public class DataNode extends Configured
- implements InterDatanodeProtocol, ClientDatanodeProtocol, FSConstants,
- Runnable, DataNodeMXBean {
它实现了 InterDatanodeProtocol, ClientDatanodeProtocol, 这两个重要接口。 作用和之前分析haoop IPC的时候提到过, 为了是客户端 和其它datanode节点远程调用本dataNode节点方法的时候,提供方法实际运行的对象。
我们可以看到它并没有实现和datanode的接口,因为datanode是主动和nameNode联系,nameNode从来不会主动调用dataNode上的方法。
在main 方法中主要 通过一系列调用创建了datanode对象。
之后datanode的初始化工作主要由 startDataNode()来完成, 这是一个很复杂的方法,我们来一点一点的分析。
- void startDataNode(Configuration conf,
- AbstractList<File> dataDirs, SecureResources resources
- ) throws IOException {
- if(UserGroupInformation.isSecurityEnabled() && resources == null)
- throw new RuntimeException("Cannot start secure cluster without " +
- "privileged resources.");
- // connect to name node
- this.namenode = (DatanodeProtocol)
- RPC.waitForProxy(DatanodeProtocol.class,
- DatanodeProtocol.versionID,
- nameNodeAddr,
- conf);
- 这个是通过反射获取同dataNode节点通信的代理对象
- // get version and id info from the name-node
- NamespaceInfo nsInfo = handshake(); //立刻与名字节点通信
- StartupOption startOpt = getStartupOption(conf);
- assert startOpt != null : "Startup option must be set.";
- storage.recoverTransitionRead(nsInfo, dataDirs, startOpt);
- // adjust
- this.dnRegistration.setStorageInfo(storage);
- // initialize data node internal structure
- this.data = new FSDataset(storage, conf);
- // 创建数据存储KV 的对象 这个后面还要再细分析。
- }
- this.threadGroup = new ThreadGroup("dataXceiverServer");
- this.dataXceiverServer = new Daemon(threadGroup,
- new DataXceiverServer(ss, conf, this));
- this.threadGroup.setDaemon(true); // 创建流接口服务器 DataXServer 这个需要后面再分析
- ipcServer = RPC.getServer(this, ipcAddr.getHostName(), ipcAddr.getPort(),
- conf.getInt("dfs.datanode.handler.count", 3), false, conf, //创建IPC服务器。
- blockTokenSecretManager);
-
- }
上面代码分析中我们留了两个之后还要分析的方法,现在来看一下。
第一个是FsdataSet.
我们需要考虑的问题是 hadoop以64M大小为单位作为一个文件的大小 存储在linux 文件系统 上。 当文件多了,就有一个效率问题,同一个文件夹下有过多的文件
和文件目录过深都不利于检索速度(这个与linux文件系统inode结构有关,这里暂不讨论这个) 。所以我们这里要设计一个结构 需要创建文件夹 但文件夹目录不能过深。
此外 hadoop 还考虑了一个优化问题,如果一个datanode节点上插有多块硬盘的话,怎么提高并行吞吐量。好,有了这些我们来看具体实现。
一个FSdir对于着一个存储目录,一个FSVolume 对应着一个用户配置的数据目录(应该为一个磁盘最好) FsVolumeSet存储着所有的FSVolume对象。
在FsDataSet中海油一个最重要的成员变量,volumeMap 就是这个成员变量存储了 每一个Block 和它对应的存储路径等信息。
- HashMap<Block,DatanodeBlockInfo> volumeMap = new HashMap<Block, DatanodeBlockInfo>();;
第二个是 DataXServer
当往数据节点中填入数据或者数据节点之间做负载均衡的时候显然无法 使用Hdoop IPC 因为hadoop的IPC 在socket之上封装了方法的调用,如果在这之上封装一个大规模数据传输的方法,显然效率上不如直接用socket通信。
- ServerSocket ss;
- if(secureResources == null) {
- ss = (socketWriteTimeout > 0) ?
- ServerSocketChannel.open().socket() : new ServerSocket();
- Server.bind(ss, socAddr, 0);
- } else {
- ss = resources.getStreamingSocket();
- }
- ss.setReceiveBufferSize(DEFAULT_DATA_SOCKET_SIZE);
- //初始化处理类dataXceiverServer
- this.threadGroup = new ThreadGroup("dataXceiverServer");
- this.dataXceiverServer = new Daemon(threadGroup, new DataXceiverServer(ss, conf, this));
- this.threadGroup.setDaemon(true); // auto destroy when empty
DataXceiverServer 是个线程 我们看一下它的ruan方法
- Socket s = ss.accept();
- s.setTcpNoDelay(true);
- new Daemon(datanode.threadGroup,
- new DataXceiver(s, datanode, this)).start();
我们再看一下 DataXceiver的run方法
- public void run() {
- DataInputStream in=null;
- try {
- in = new DataInputStream(
- new BufferedInputStream(NetUtils.getInputStream(s),
- SMALL_BUFFER_SIZE));
- short version = in.readShort();
- if ( version != DataTransferProtocol.DATA_TRANSFER_VERSION ) {
- throw new IOException( "Version Mismatch" );
- }
- boolean local = s.getInetAddress().equals(s.getLocalAddress());
- byte op = in.readByte();
- // Make sure the xciver count is not exceeded
- int curXceiverCount = datanode.getXceiverCount();
- if (curXceiverCount > dataXceiverServer.maxXceiverCount) {
- throw new IOException("xceiverCount " + curXceiverCount
- + " exceeds the limit of concurrent xcievers "
- + dataXceiverServer.maxXceiverCount);
- }
- long startTime = DataNode.now();
- switch ( op ) {
- case DataTransferProtocol.OP_READ_BLOCK:
- readBlock( in );
- datanode.myMetrics.addReadBlockOp(DataNode.now() - startTime);
- if (local)
- datanode.myMetrics.incrReadsFromLocalClient();
- else
- datanode.myMetrics.incrReadsFromRemoteClient();
- break;
- case DataTransferProtocol.OP_WRITE_BLOCK:
- writeBlock( in );
- datanode.myMetrics.addWriteBlockOp(DataNode.now() - startTime);
- if (local)
- datanode.myMetrics.incrWritesFromLocalClient();
- else
- datanode.myMetrics.incrWritesFromRemoteClient();
- break;
- case DataTransferProtocol.OP_REPLACE_BLOCK: // for balancing purpose; send to a destination
- replaceBlock(in);
- datanode.myMetrics.addReplaceBlockOp(DataNode.now() - startTime);
- break;
- case DataTransferProtocol.OP_COPY_BLOCK:
- // for balancing purpose; send to a proxy source
- copyBlock(in);
- datanode.myMetrics.addCopyBlockOp(DataNode.now() - startTime);
- break;
- case DataTransferProtocol.OP_BLOCK_CHECKSUM: //get the checksum of a block
- getBlockChecksum(in);
- datanode.myMetrics.addBlockChecksumOp(DataNode.now() - startTime);
- break;
- default:
- throw new IOException("Unknown opcode " + op + " in data stream");
- }
- } catch (Throwable t) {
- LOG.error(datanode.dnRegistration + ":DataXceiver",t);
- } finally {
- LOG.debug(datanode.dnRegistration + ":Number of active connections is: "
- + datanode.getXceiverCount());
- IOUtils.closeStream(in);
- IOUtils.closeSocket(s);
- dataXceiverServer.childSockets.remove(s);
- }
- }
重点在这句
- byte op = in.readByte();
应该是根据流中的事先约定 来 第一个字节 来决定是