创建NameNode的入口为NameNode.main(String[] argv),主要的创建工作在方法NameNode.createNameNode(String[] argv, Configuration conf)中,分析如下:
一.【parseArguments(argv)】:解析启动NameNode命令传来的参数,启动参数有:“-format,-regular,-upgrade,-recover,-force,-rollback,-finalize,-importCheckpoint,-nonInteractive”,但NameNode只处理以下三个参数:“-format”,“-finalize”,“-recover”。此三个参数的处理逻辑分别见第二,三,四步:
二.若是“-format”,调用【format(Configuration conf,boolean isConfirmationNeeded, boolean isInteractive)】,具体逻辑如下:
1)【FSNamesystem.getNamespaceDirs(Configuration conf)】:获取“dfs.name.dir”参数的值(可配置多个),若没有,则默认设置一个值“/tmp/hadoop/dfs/name”;按此参数值依次初始化File对象,存入Collection<File>类的实例dirsToFormat中并返回;
2)【FSNamesystem.getNamespaceEditsDirs(Configuration conf)】:获取“dfs.name.edits.dir”参数的值(可配置多个),若没有,则默认设置为“/tmp/hadoop/dfs/name”;按此参数值依次初始化File对象,存入Collection<File>类的实例editDirsToFormat中并返回;
3)【FSImage.FSImage(Collection<File> fsDirs, Collection<File> fsEditsDirs) 】:初始化FSImage对象;
3.1)将FSImage继承至Storage类的成员变量storageType设置为NodeType.NAME_NODE;
3.2)【FSImage.setStorageDirectories(Collection<File> fsNameDirs, Collection<File> fsEditsDirs)】:设置(Storage)FSImage.storageDirs队列的值;
3.2.1)【Storage.StorageDirectory.StorageDirectory(File dir,StorageDirType dirType)】:遍历dirsToFormat(见第1步)集合----初始化StorageDirectory对象,其中若${dfs.name.dir}值与其中一个${dfs.name.edits.dir}相同,则dirType=NameNodeDirType.IMAGE_AND_EDITS,否则dirType=NameNodeDirType.IMAGE,并添加到(Storage)FSImage.storageDirs队列中;
3.2.2)【Storage.StorageDirectory.StorageDirectory(File dir,StorageDirType dirType)】:遍历editDirsToFormat(见第2步)集合----初始化StorageDirectory对象,其中dirType=NameNodeDirType.EDITS,并添加到(Storage)FSImage.storageDirs队列中;
4)【FSNamesystem.FSNamesystem(FSImage fsImage,Configuration conf) 】:初始化FSNamesystem对象:
4.1)【FSNamesystem.setConfigurationParameters(Configuration conf)】:读取配置文件中的参数初始化FSNamesystem中成员变量的值
4.1.1)FSNamesystem.fsNamesystemObject = this;
4.1.2)初始化clusterMap,replicator,maxReplication,minReplication,defaultBlockSize等变量;
4.2)【FSDirectory.FSDirectory(FSImage fsImage, FSNamesystem ns, Configuration conf)】:初始化FSDirectory对象,赋给FSNamesystem.dir变量
4.2.1)【INodeDirectoryWithQuota.INodeDirectoryWithQuota(String name, PermissionStatus permissions, long nsQuota, long dsQuota)】:初始化FSDirectory.rootDir变量;其中,name=INodeDirectory.ROOT_NAME=“”;
4.2.2)FSDirectory.fsImage = fsImage;
4.2.3)FSDirectory.namesystem= ns;
5)【FSImage.format()】:格式化NameNode;
5.1)初始化layoutVersion,namespaceID,checkpointTime(等于当前时间)等信息;
5.2)【FSImage.format(StorageDirectory sd) 】:遍历第3.2步中的storageDirs队列,依次调用该方法,此方法的具体逻辑如下:
5.2.1)【StorageDirectory.clearDirectory() 】:删除current文件夹并重新创建此文件夹;
5.2.2)【StorageDirectory.lock() 】:锁住此目录;
5.2.3)【FSImage.saveCurrent(StorageDirectory sd)】:
5.2.3.1)若dirType=NameNodeDirType.IMAGE,则调用【FSImage.getImageFile(StorageDirectory sd,NameNodeFile type)】初始化FSImage文件的File对象,然后以此File对象为参数调用【FSImage.saveFSImage(File newFile)】,将第4.2.1步中生成的INodeDirectoryWithQuota写入FSImage文件中;
5.2.3.2)若dirType=NameNodeDirType.EDITS,则调用【FSEditLog.createEditLogFile(File name)】创建edits文件的数据输出流--EditLogFileOutputStream
5.2.3.3)【StorageDirectory.write() 】:生成VESION文件;
5.2.4)【StorageDirectory.unlock() 】:解锁;
上诉逻辑完成之后调用【System.exit(int status)】直接退出。
三.若是“-finalize”,调用【NameNode.finalize(Configuration conf, boolean isConfirmationNeeded) 】,前四步与“-format”一样,第5步是调用【FSImage.finalizeUpgrade()】,遍历storageDirs队列,删除previous文件夹内容;
四.若是“-recover”,调用【NameNode.doRecovery(StartupOption startOpt, Configuration conf)】,前4步与“-format”一样,第5步调用【FSImage.loadFSImage(MetaRecoveryContext recovery)】方法,处理逻辑如下:
1)遍历storageDirs队列,执行如下处理逻辑:
1.1)检查${dfs.name.dir}/current中是否存在VESION文件,若不存在,则needToSave标志位置为true,并跳过下面的逻辑继续遍历下一个StorageDirectory对象;
1.2)判断${dfs.name.dir}/current中是否存在fsimage文件,并标记到imageExists变量,并将${dfs.name.dir}值保存到Collection<String>:imageDirs中;
1.3)判断${dfs.name.dir}/current中是否存在edits文件,并标记到editsExists变量,并将${dfs.name.dir}值保存到Collection<String>:editsDirs中;
1.4)【FSImage.readCheckpointTime(StorageDirectory sd) 】:判断${dfs.name.dir}/current中是否存在fstime文件,若存在则读取CheckpointTime并存入FSImage.checkpointTime变量中;
2)【FSImage.recoverInterruptedCheckpoint(StorageDirectory nameSD, StorageDirectory editsSD)】若fsimage.ckpt文件不存在,则直接返回false;否则:判断edits.new文件是否存在,
2.1)若存在则直接删除并返回true;之所以删除是因为只有当SecondaryNameNode在合并fsimage和edits的时候才生成edits.new文件,目前检查有此文件,则可能SecondaryNameNode已经将合并的fsimage上到主NameNode,故直接删除并返回;
2.2)若不存在则将fsimage.ckpt文件更名为fsimage, 返回true;
3)【Storage.StorageDirectory.read() 】从VESION文件读取(StorageInfo)Storage.layoutVersion,storageType,namespaceID,cTime信息;
4)【FSImage.loadFSImage(File curFile) 】加载fsimage文件:
4.1)对fsimage文件创建DataInputStream流;
4.2)依次读取imgVersion,namespaceID,layoutVersion,numFiles等信息;
4.3)取FSNamesystem.dir.rootDir作为根节点
4.4)根据numFiles的大小,重复解析numFiles次fsimage文件,生成一个文件的目录树结构,解析主要过程如下:
4.4.1)读取file的路径信息path;读取此文件对应的block块个数numBlocks;
4.4.2)继续重复解析numBlocks次fsimage文件,从FSImage文件中读取文件对应的blockid,numBytes,generationStamp信息,封装成Block对象,可能会有多个block,则放入Block[]数组中;
4.4.3)判断此path是否为上一次遍历时path(保存到parentPath变量中,第一次为“”)的子路径,若不是则parentINode=null,path=path.substring(0, path.lastIndexOf(‘/’));
4.4.4)【FSDirectory.addToParent(String src, INodeDirectory parentINode, PermissionStatus permissions, Block[] blocks, short replication, long
modificationTime, long atime, long nsQuota, long dsQuota, long preferredBlockSize)】,
4.4.4.1)若Block[]数组为空,说明是目录,则初始化INodeDirectory或者INodeDirectoryWithQuota对象;若不为空,则初始化INodeFile对象;以上两个类均是INode的子类
4.4.4.2)【rootDir.addToParent(String path, INode newNode,INodeDirectory parent, boolean inheritPermission)】:将上一步产生的newNode添加到树形目录结构(FSDirectory.rootDir)中;
1) 【INodeDirectory.getExistingPathINodes(byte[][] components, INode[] existing)】:在已存在的目录树结构中查找path的最后一个文件夹的父节点Node:parentNode, 例如:若path=“/c1,则找到父节点为根节点rootDir;若path="/c1/c2",已有目录结构有"/c1",则返回父节点c1,然后将路径中最后一个文件夹存入父节点的childen中;在fsimage文件中存储的二进制顺序是先存储根路径,然后是一级路径,二级路径.....(因为在写fsimage文件时时遍历的目录树,故有此顺序),否则若先存储了"/c1/c2",那么在查找"/c1/c2"的父节点时将会为null;
2)newNode.name = pathComponents[pathComponents.length-1];
3)【parentNode.addChild(T node, boolean inheritPermission)】将newNode添加到目录树结构中;作为parentNode的孩子节点存入children队列中,并将newNode.parent= parentNode;
4.4.4.3)将4.4.4.1步中的newNode的name转换成ByteArray放入FSDirectory.nameCache中;
4.4.4.4)若Block[]数组不为空,遍历Block[],根据数组中到Block元素和replication大小,生成BlockInfo对象;并存入FSNamesystem.blocksMap和INodeFile的BlockInfo[]中;replication值是用于初始化BlockInfo对象到Object[] triplets大小的;
4.5)【FSImage.loadFilesUnderConstruction(int version, DataInputStream in, FSNamesystem fs)】加载正在创建的文件:
4.5.1)【FSImage.readINodeUnderConstruction(DataInputStream in) 】解析fsimage文件中的正在创建的文件部分信息,生成INodeFileUnderConstruction对象;
4.5.2)根据path信息在目录文件树结构中查找INode信息,若没有则抛异常;若有,则调用【FSDirectory.replaceNode(String path, INodeFile oldnode, INodeFile newnode)】用INodeFileUnderConstruction对象替换找到的旧INode对象;
4.5.2.1)将旧INode对象的parent=null,
4.5.2.2)【INodeDirectory.addNode(String path, INodeFile newNode)】,将INodeFileUnderConstruction对象添加到目录结构中;
4.5.2.3)获取INodeFileUnderConstruction对象的Block[]数组,遍历Block[],根据数组中到Block元素和replication大小,生成BlockInfo对象;并存入FSNamesystem.blocksMap和INodeFile的BlockInfo[]中;replication值是用于初始化BlockInfo对象到Object[] triplets大小的;
5)【FSImage.saveNamespace(boolean renewCheckpointTime),renewCheckpointTime=true】
5.1)更新FSImage.checkpointTime为当前时间;
5.2)当存在VERSION文件时,将current文件夹更名为lastcheckpoint.tmp,并重新创建current文件夹;
五.根据启动的参数选择执行上述二/三/四步后,调用【NameNode.NameNode(Configuration conf)】初始化NameNode,内部调用【NameNode.initialize(Configuration conf)】
1)获取“dfs.namenode.rpc-address”配置参数的值,若没有则缺省取“fs.default.name”参数的值,并用此地址来创建NameNode.server:Server;
2)【FSNamesystem.FSNamesystem(NameNode nn, Configuration conf)】,内部调用【FSNamesystem.initialize(NameNode nn, Configuration conf) 】,目的:初始化NameNode.namesystem变量;
2.1)【FSNamesystem.setConfigurationParameters(Configuration conf)】:读取配置文件中的参数初始化FSNamesystem中成员变量的值
2.2)NameNode.serverAddress变量的引用赋值给FSNamesystem.nameNodeAddress变量;
2.3)【FSDirectory.FSDirectory(FSNamesystem ns, Configuration conf)】,内部调用【FSDirectory.FSDirectory(FSImage fsImage, FSNamesystem ns, Configuration conf)】,与(二.4.2)的区别是FSImage对象的不同,在此步中初始化的FSImage对象,其storageDirs队列为空,并初始化FSImage.editLog变量。初始化FSDirectory对象,赋给FSNamesystem.dir变量;
2.4)【FSDirectory.loadFSImage(Collection<File> dataDirs, Collection<File> editsDirs, StartupOption startOpt)】,加载FSImage文件,其中形参dataDirs和editsDirs通过(二.1)和(二.2)中的方法得到;
2.4.1)判断startOpt是否等于“-format”,若等于,则将dataDirs和editsDirs封装成StorageDirectory对象并添加到(Storage)FSImage.storageDirs队列中;然后调用【FSImage.format()】遍历FSImage.storageDirs队列,再依次调用【FSImage.format(StorageDirectory sd)】格式化NameNode,此步的逻辑与(二.5)一样;
2.4.2)【FSImage.recoverTransitionRead(Collection<File> dataDirs, Collection<File> editsDirs, StartupOption startOpt)】
2.4.2.1)【FSImage.setStorageDirectories(Collection<File> fsNameDirs, Collection<File> fsEditsDirs)】:设置(Storage)FSImage.storageDirs队列的值,因为是List<StorageDirectory>,故若存在同一个StorageDirectory对象则覆盖;
2.4.2.2) 遍历所有的StorageDirectory对象,调用【StorageDirectory.analyzeStorage(StartupOption startOpt) 】,检查所有${dfs.name.dir}目录下文件的一致性,大致思路如下:
1)检测${dfs.name.dir}文件夹是否存在,是否有写权限;
2)根据VERSION,previous,previous.tmp,finalized.tmp,lastcheckpoint.tmp文件的存在与否来判断文件的状态;
2.4.2.3)对于状态为StorageState.NOT_FORMATTED的,调用【StorageDirectory.clearDirectory()】,参考(二.5.2.1);
2.4.2.4)若startOpt等于“-upgrade”,调用【FSImage.doUpgrade()】
2.4.2.5)若startOpt等于“-importCheckpoint“,调用【FSImage.doImportCheckpoint()】
2.4.2.6)若startOpt等于”-rollback”,调用【FSImage.doRollback() 】
2.4.2.7)【FSImage.loadFSImage(MetaRecoveryContext recovery)】,与第四步中的第5步一样;
2.4.3)若loadFSImage(MetaRecoveryContext recovery)返回true,表示需要保存,则调用【FSImage.saveNamespace(boolean renewCheckpointTime),renewCheckpointTime=true】,参考(四.5)步;
2.4.4)【FSEditLog.open()】遍历所有的EDITS类型的文件,并对所有edits文件创建EditLogOutputStream流,添加到FSEditLog.editStreams:ArrayList<EditLogOutputStream>变量中;
2.5)启动HeartbeatMonitor,LeaseManager.Monitor,ReplicationMonitor线程;
2.6)初始化FSNamesystem.dnsToSwitchMapping:DNSToSwitchMapping变量;此dnsToSwitchMapping变量对NameNode生成DataNode节点的网络拓扑图很重要的,其作用:NameNode把一个ip地址解析成一个路径的形式,从而NameNode把注册的DataNode节点按照IP转换成的路径存储到一个树状网络拓扑图中(对应Networktopology的一个实例)。用户有两种方式来定义ip地址转换路径的解析规则:第一,编写一个实现DNSToSwitchMapping接口的类,并在配置文件的参数“topology.node.switch.mapping.impl”中指定此类,则NameNode就可以自动调用用户的解析实现;第二,HDFS已经定义了CachedDNSToSwitchMapping的实现类ScriptBasedMapping,此类引用ScriptBasedMapping的内部静态类RawScriptBasedMapping(此类实现DNSToSwitchMapping接口);它通过执行shell脚本语言调用第三方的实现来获取一批ip地址的解析路径结果;
2.6.1)从配置文件中获取“topology.node.switch.mapping.impl”参数值,并通过反射初始化为DNSToSwitchMapping对象,此参数值缺省为ScriptBasedMapping实例;
2.7)【DNSToSwitchMapping.resolve(List<String> names)】调用此方法对配置文件参数“dfs.hosts”值(此参数表示运行访问NameNode的host列表)进行路径解析;
2.8) 获取"dfs.namenode.rpc-address"配置参数的值,若没有则缺省取“fs.default.name”参数的值,用此参数值初始化InetSocketAddress对象,并将对象的hostname变量赋值给FSNamesystem.nameNodeHostName;
3)获取“dfs.namenode.servicerpc-address”配置参数的值,若没有则缺省取“fs.default.name”参数的值,并用此参数初始化InetSocketAddress对象,然后创建NameNode.serviceRpcServer:Server;( BackupNode, Datanodes以及其他的服务都连接此Server);
4)【NameNode.startHttpServer(Configuration conf)】利用jetty容器注册一个HTTPServer——NameNode.httpServer:HttpServer
4.1)定义过滤器AuthenticationFilter;
4.2)注册的Servlet有:GetDelegationTokenServlet,RenewDelegationTokenServlet,CancelDelegationTokenServlet,,FsckServlet,GetImageServlet,ListPathsServlet,FileDataServlet,FileChecksumServlets.RedirectServlet,ContentSummaryServlet等;
4.3)【HttpServer.start()】启动HTTPServer
5)启动serviceRpcServer和server;
6)读取“dfs.namenode.plugins”参数配置的插件,然后启动;
-------------------------------------------------------
NameNode创建之后的结果:
1)生成了文件的目录树结构,叶子节点INodeFile中保护了此文件对应的所有BlockInfo;
2)系统中所有文件对应的BlockInfo都存入了FSNamesystem.blocksMap中;
-------------------------------------------------------
BlocksMap数据结构分析:
BlocksMap保存HDFS中的所有block,它的数据结构:
BlocksMap的数据实质上数据是存入LightWeightGSet<Block, BlockInfo>对象;
LightWeightGSet的数据结构是一个数组元素为LinkedElement的数组(数组长度与内存大小有关),而LinkedElement是一个链表;在put操作时通过hash算法得到数组的位置i,将新元素插入数组位置i的链表的头部;