datanode的介绍
一个典型的HDFS系统包括一个NameNode和多个DataNode。DataNode是hdfs文件系统中真正存储数据的节点。
每个DataNode周期性和唯一的NameNode通信,还时不时和hdfs客户端代码以及其他datanode通信。
datanode维护一个重要的表:
块=>字节流
这些存储在本地磁盘,DataNode在启动时,还有启动后周期性报告给NameNode,这个表的内容。
DataNodes周期性请求NameNode询问命令操作,NameNode不能直接连接DataNode,NameNode在DataNode调用时,简单返回值。
DataNodes还维护一个开放的socket服务器,让客户端代码和其他DataNode通过它可以读写数据,这个服务器的host/port会汇报给NameNode。
datanode启动流程
在命令行启动datanode的方法是:bin/hadoop datanode
查看bin/hadoop脚本,可以看到最后执行的java类是:org.apache.hadoop.hdfs.server.datanode.DataNode
DataNode的骨架成员如下:
public class DataNode extends Configured implements InterDatanodeProtocol, ClientDatanodeProtocol, FSConstants,Runnable, DataNodeMXBean { public DatanodeProtocol namenode = null;//与NameNode通信的ipc客户端类 public FSDatasetInterface data = null;//管理一系列的数据块,每个块在本地磁盘上都有唯一的名字和扩展名。所有和数据块相关的操作,都在FSDataset相关的类中进行处理。 public DatanodeRegistration dnRegistration = null;//DataNode向NameNode的注册信息,包含名字(datanode机器名:dfs.datanode.address端口),info的http端口,ipc的端口等 volatile boolean shouldRun = true;//DataNode循环运行标志,为true就一直运行 private LinkedList<Block> receivedBlockList = new LinkedList<Block>();//已经接收的数据块,定期通知namenode接收完毕时,会移除 private final Map<Block, Block> ongoingRecovery = new HashMap<Block, Block>();//存放正在从本地块恢复到其他DataNode的数据块,恢复完毕后移除,在其他DataNode的数据块副本损坏或丢失时会使用 private LinkedList<String> delHints = new LinkedList<String>(); //需要删除的块,一般是被替换时才会被删除,也是在定期通知namenode后,会移除 Daemon dataXceiverServer = null;//用于读写数据的服务器,接收客户端和其他DataNode的请求,它不用于内部hadoop ipc机制,端口是dfs.datanode.address public Server ipcServer; //内部datanode调用的ipc服务器,用于客户端,端口是dfs.datanode.ipc.address long blockReportInterval;//数据块报告周期,默认是60*60秒,即一个小时 long lastBlockReport = 0;//记录最近的数据块报告时间,与blockReportInterval联合使用 long lastHeartbeat = 0;//记录最近和namenode的心跳时间 long heartBeatInterval;//和namenode的心跳周期,默认是3s private DataStorage storage = null;//DataStorage提供了format方法,用于创建DataNode上的Storage,对DataNode的升级/回滚/提交过程,就是对DataStorage的doUpgrade/doRollback/doFinalize分析得到的。同时,利用StorageDirectory,DataStorage管理存储系统的状态。 private HttpServer infoServer = null;//查看DataNode状态信息的http服务器,端口是dfs.datanode.http.address public DataBlockScanner blockScanner = null;//检测它所管理的所有Block数据块的一致性,因此,对已DataNode节点上的每一个Block,它都会每隔scanPeriod ms(默认三个星期)利用Block对应的校验和文件来检测该Block一次,看看这个Block的数据是否已经损坏。 public Daemon blockScannerThread = null; }
DataNode的初始化和启动:
public class DataNode extends Configured implements InterDatanodeProtocol, ClientDatanodeProtocol, FSConstants,Runnable, DataNodeMXBean { //main方法,DataNode的入口点 public static void main(String args[]) { secureMain(args, null); } //主线程阻塞,让DataNode的任务循环执行 public static void secureMain(String [] args, SecureResources resources) { try { ... DataNode datanode = createDataNode(args, null, resources); if (datanode != null) datanode.join(); } ... } public static DataNode createDataNode(String args[],Configuration conf, SecureResources resources) throws IOException { DataNode dn = instantiateDataNode(args, conf, resources); runDatanodeDaemon(dn);//DataNode类作为一个Thread运行 return dn; } public static DataNode instantiateDataNode(String args[],Configuration conf, SecureResources resources) throws IOException { ... String[] dataDirs = conf.getStrings(DATA_DIR_KEY);//获取DataNode管理的本地目录集合 return makeInstance(dataDirs, conf, resources); } //检查本地目录集合的合法性 public static DataNode makeInstance(String[] dataDirs, Configuration conf, SecureResources resources) throws IOException { ... ArrayList<File> dirs = new ArrayList<File>(); FsPermission dataDirPermission = new FsPermission(conf.get(DATA_DIR_PERMISSION_KEY, DEFAULT_DATA_DIR_PERMISSION)); for (String dir : dataDirs) { ... DiskChecker.checkDir(localFS, new Path(dir), dataDirPermission); dirs.add(new File(dir)); ... } if (dirs.size() > 0) return new DataNode(conf, dirs, resources); return null; } //实例化DataNode DataNode(final Configuration conf,final AbstractList<File> dataDirs, SecureResources resources) throws IOException { super(conf); ... try { startDataNode(conf, dataDirs, resources); } catch (IOException ie) { shutdown(); throw ie; } } void startDataNode(Configuration conf, AbstractList<File> dataDirs, SecureResources resources) throws IOException { InetSocketAddress nameNodeAddr = NameNode.getServiceAddress(conf, true); InetSocketAddress socAddr = DataNode.getStreamingAddr(conf);//获取DataNode的数据块流的读写的端口 int tmpPort = socAddr.getPort(); storage = new DataStorage();//管理数据目录的类,完成格式化,升级,回滚等功能 // construct registration this.dnRegistration = new DatanodeRegistration(machineName + ":" + tmpPort); //与namenode通信的客户端类 this.namenode = (DatanodeProtocol) RPC.waitForProxy(DatanodeProtocol.class,DatanodeProtocol.versionID,nameNodeAddr, conf); //从NameNode获取版本和id信息 NamespaceInfo nsInfo = handshake(); if (simulatedFSDataset) { ... } else { // real storage // read storage info, lock data dirs and transition fs state if necessary storage.recoverTransitionRead(nsInfo, dataDirs, startOpt); // adjust this.dnRegistration.setStorageInfo(storage); // initialize data node internal structure this.data = new FSDataset(storage, conf);//一切数据块读写的实际操作类 } ... this.dataXceiverServer = new Daemon(threadGroup, new DataXceiverServer(ss, conf, this));//初始化数据块的流读写服务器 ... //初始化数据块报告周期,默认是一个小时 this.blockReportInterval = conf.getLong("dfs.blockreport.intervalMsec", BLOCKREPORT_INTERVAL); ... //初始化与namenode心跳周期,默认是3秒 this.heartBeatInterval = conf.getLong("dfs.heartbeat.interval", HEARTBEAT_INTERVAL) * 1000L; ... if ( reason == null ) { blockScanner = new DataBlockScanner(this, (FSDataset)data, conf);//初始化数据块一致性检测类 } ... //DataNode的状态信息查询的http服务器地址 InetSocketAddress infoSocAddr = DataNode.getInfoAddr(conf); ... //初始化DataNode的状态信息查询的http服务器 this.infoServer = (secureResources == null) ? new HttpServer("datanode", infoHost, tmpInfoPort, tmpInfoPort == 0, conf, SecurityUtil.getAdminAcls(conf, DFSConfigKeys.DFS_ADMIN)) : new HttpServer("datanode", infoHost, tmpInfoPort, tmpInfoPort == 0, conf, SecurityUtil.getAdminAcls(conf, DFSConfigKeys.DFS_ADMIN), secureResources.getListener()); ... //添加infoServer一些Servlet的映射url和类 ... this.infoServer.start(); ... //初始化内部hadoop ipc服务器 InetSocketAddress ipcAddr = NetUtils.createSocketAddr( conf.get("dfs.datanode.ipc.address")); ipcServer = RPC.getServer(this, ipcAddr.getHostName(), ipcAddr.getPort(), conf.getInt("dfs.datanode.handler.count", 3), false, conf, blockTokenSecretManager); dnRegistration.setIpcPort(ipcServer.getListenerAddress().getPort()); ... }
DataNode的服务:
//运行DataNode的后台线程 public static void runDatanodeDaemon(DataNode dn) throws IOException { if (dn != null) { //register datanode dn.register(); dn.dataNodeThread = new Thread(dn, dnThreadName); dn.dataNodeThread.setDaemon(true); dn.dataNodeThread.start(); } } //启动数据块的流读写服务器,内部hadoop ipc服务器 public void run() { ... dataXceiverServer.start(); ipcServer.start(); while (shouldRun) { try { startDistributedUpgradeIfNeeded();//检测是否需要升级hadoop文件系统 offerService();//DataNode提供服务,定时发送心跳给NameNode,响应NameNode返回的命令并执行 } ... } } //DataNode提供服务,定时发送心跳给NameNode,响应NameNode返回的命令并执行,通知namenode接收完毕的数据块和删除的数据块,定时上报数据块 public void offerService() throws Exception { ... while (shouldRun) { try { long startTime = now(); ... if (startTime - lastHeartbeat > heartBeatInterval) { lastHeartbeat = startTime; //定期发送心跳给NameNode DatanodeCommand[] cmds = namenode.sendHeartbeat(dnRegistration, data.getCapacity(), data.getDfsUsed(), data.getRemaining(), xmitsInProgress.get(), getXceiverCount()); ... //响应namenode返回的命令做处理 if (!processCommand(cmds)) continue; } synchronized(receivedBlockList) { synchronized(delHints) { blockArray = receivedBlockList.toArray(new Block[numBlocks]); delHintArray = delHints.toArray(new String[numBlocks]); } } } if (blockArray != null) { //通知NameNode已经接收完毕的块,以及删除的块 namenode.blockReceived(dnRegistration, blockArray, delHintArray); synchronized (receivedBlockList) { synchronized (delHints) { for(int i=0; i<blockArray.length; i++) { receivedBlockList.remove(blockArray[i]);//清空保存接收完毕的块 delHints.remove(delHintArray[i]);//清空保存删除完毕的块 } } } } if (startTime - lastBlockReport > blockReportInterval) { if (data.isAsyncBlockReportReady()) { // Create block report ... Block[] bReport = data.retrieveAsyncBlockReport(); ... //向NameNode上报数据块信息 DatanodeCommand cmd = namenode.blockReport(dnRegistration, BlockListAsLongs.convertToArrayLongs(bReport)); ... processCommand(cmd); } else { //请求异步准备好数据块上报信息 data.requestAsyncBlockReport(); ... } } } } // while (shouldRun) } // offerService }
以上就是DataNode的启动流程和服务流程,都以作适当删减,留下主干,加上注释。
DataNode的相关重要类
FSDataset:所有和数据块相关的操作,都在FSDataset相关的类。详细分析参考 http://caibinbupt.iteye.com/blog/284365
DataXceiverServer:处理数据块的流读写的的服务器,处理逻辑由DataXceiver完成。详细分析参考 http://caibinbupt.iteye.com/blog/284979
DataXceiver:处理数据块的流读写的线程。详细分析参考 http://caibinbupt.iteye.com/blog/284979
还有处理非读写的非主流的流程。详细分析参考 http://caibinbupt.iteye.com/blog/286533
BlockReceiver:完成数据块的流写操作。详细分析参考 http://caibinbupt.iteye.com/blog/286259
BlockSender:完成数据块的流读操作。
DataBlockScanner:用于定时对数据块文件进行校验。详细分析参考http://caibinbupt.iteye.com/blog/286650
总结
上面讲了DataNode相关的核心类的成员和初始化流程,并做了代码的删减,留下主干,加上注释,让初学者可以概览DataNode的源码,快速入门。