简单地讲,NameNode的初始化,对应到代码上就是调用main方法,在main方法中调用NameNode namenode = createNameNode(argv, null);
在createNameNode方法中对format和finalize两种状态进行处理,如果不是这两种状态那么,调用NameNode namenode = new NameNode(conf);
在NameNode的构造方法中会调用initialize方法,下面就详细看看initialize方法中都做了哪些事情:
简单地说,initialize方法中主要做了以下几项工作:
初始化NameNode的成员变量,包括创建RPC服务器,初始化FSNamesystem,启动RPC服务器和回收站线程。
1.创建RPC服务器
首先是InetSocketAddress socAddr = NameNode.getAddress(conf);进去看了下这个地址就是我们在core-site.xml文件中设置的“fs.default.name”的值,例如“hdfs://127.0.0.1:9000”
然后调用了 this.server = RPC.getServer(this, socAddr.getHostName(), socAddr.getPort(), handlerCount, false, conf);这个是创建一个RPC的server,看看具体里面都做了什么:
getServer()----》Server():
下面是Server的构造方法:
protected Server(String bindAddress, int port,
Class<? extends Writable> paramClass, int handlerCount,
Configuration conf, String serverName)
throws IOException {
this.bindAddress = bindAddress;
this.conf = conf;
this.port = port;
this.paramClass = paramClass;
this.handlerCount = handlerCount;
this.socketSendBufferSize = 0;
this.maxQueueSize = handlerCount * MAX_QUEUE_SIZE_PER_HANDLER;
this.callQueue = new LinkedBlockingQueue<Call>(maxQueueSize);
this.maxIdleTime = 2*conf.getInt("ipc.client.connection.maxidletime", 1000);
this.maxConnectionsToNuke = conf.getInt("ipc.client.kill.max", 10);
this.thresholdIdleConnections = conf.getInt("ipc.client.idlethreshold", 4000);
// Start the listener here and let it bind to the port
listener = new Listener();
this.port = listener.getAddress().getPort();
this.rpcMetrics = new RpcMetrics(serverName,
Integer.toString(this.port), this);
this.tcpNoDelay = conf.getBoolean("ipc.server.tcpnodelay", false);
// Create the responder here
responder = new Responder();
}
可以很清楚地看到在构造函数中,主要是三方面的工作,一个是一些配置文件中设置的变量的初始化,一个是创建一个listener线程,一个是创建一个reponder线程。
2.初始化FSNamesystem
this.namesystem = new FSNamesystem(this, conf);
FSNamesystem的构造方法会调用initialize方法,代码如下:
private void initialize(NameNode nn, Configuration conf) throws IOException {
this.systemStart = now();
setConfigurationParameters(conf);
this.nameNodeAddress = nn.getNameNodeAddress();
this.registerMBean(conf); // register the MBean for the FSNamesystemStutus
this.dir = new FSDirectory(this, conf); //load fsimage
StartupOption startOpt = NameNode.getStartupOption(conf);
this.dir.loadFSImage(getNamespaceDirs(conf),
getNamespaceEditsDirs(conf), startOpt);
long timeTakenToLoadFSImage = now() - systemStart;
LOG.info("Finished loading FSImage in " + timeTakenToLoadFSImage + " msecs");
NameNode.getNameNodeMetrics().fsImageLoadTime.set(
(int) timeTakenToLoadFSImage);
this.safeMode = new SafeModeInfo(conf);
setBlockTotal();
pendingReplications = new PendingReplicationBlocks(
conf.getInt("dfs.replication.pending.timeout.sec",
-1) * 1000L);
this.hbthread = new Daemon(new HeartbeatMonitor());
this.lmthread = new Daemon(leaseManager.new Monitor());
this.replthread = new Daemon(new ReplicationMonitor());
hbthread.start();
lmthread.start();
replthread.start();
this.hostsReader = new HostsFileReader(conf.get("dfs.hosts",""),
conf.get("dfs.hosts.exclude",""));
this.dnthread = new Daemon(new DecommissionManager(this).new Monitor(
conf.getInt("dfs.namenode.decommission.interval", 30),
conf.getInt("dfs.namenode.decommission.nodes.per.interval", 5)));
dnthread.start();
this.dnsToSwitchMapping = ReflectionUtils.newInstance(
conf.getClass("topology.node.switch.mapping.impl", ScriptBasedMapping.class,
DNSToSwitchMapping.class), conf);
/* If the dns to swith mapping supports cache, resolve network
* locations of those hosts in the include list,
* and store the mapping in the cache; so future calls to resolve
* will be fast.
*/
if (dnsToSwitchMapping instanceof CachedDNSToSwitchMapping) {
dnsToSwitchMapping.resolve(new ArrayList<String>(hostsReader.getHosts()));
}
}
从代码中可以看到,FSNamesystem的initialize方法主要的工作是:通过读取配置文件设置成员变量;创建FSDirectory并loadFSImage;设置系统安全模式;启动一系列的后台线程monitorDaemon。其中对 loadFSImage比较感兴趣,先看之。
NameNode.getStartupOption(conf);这个是从配置文件中取出“dfs.namenode.startup”这个配置项的值,如果没有设置的话,默认值为"-regular"
void loadFSImage(Collection<File> dataDirs,
Collection<File> editsDirs,
StartupOption startOpt) throws IOException {
// format before starting up if requested
if (startOpt == StartupOption.FORMAT) { //如果startOpt是“-format”,那么对其进行format过程,这个过程我在format小节已经聊过了,然后将startOpt设置为"-regular"
fsImage.setStorageDirectories(dataDirs, editsDirs);
fsImage.format();
startOpt = StartupOption.REGULAR;
}
try {
if (fsImage.recoverTransitionRead(dataDirs, editsDirs, startOpt)) { //此方法是将fsimage文件load到内存并且和edits文件merge,会调用loadFSImage和loadFSEdits方法
fsImage.saveFSImage();
}
FSEditLog editLog = fsImage.getEditLog();
assert editLog != null : "editLog must be initialized";
if (!editLog.isOpen())
editLog.open();
fsImage.setCheckpointDirectories(null, null);
} catch(IOException e) {
fsImage.close();
throw e;
}
synchronized (this) {
this.ready = true;
this.notifyAll();
}
}
3.启动RPC服务器
this.server.start(); //start RPC server
/** Starts the service. Must be called before any calls will be handled. */
public synchronized void start() throws IOException {
responder.start();
listener.start();
handlers = new Handler[handlerCount];
for (int i = 0; i < handlerCount; i++) {
handlers[i] = new Handler(i);
handlers[i].start();
}
}
可以看到server启动了三个(假设handlercount为1)线程,这三个线程分别为listener,responder,handler ,这三个线程之间是有职责关系的:Hadoop的Server采用了Java的NIO,这样的话就不需要为每一个socket连接建立一个线程,读取socket上的数据。在Server中,只需要一个线程,就可以accept新的连接请求和读取socket上的数据,这个线程,就是Listener。请求处理线程一般有多个,它们都是Server.Handle类的实例。它们的run方法循环地取出一个Server.Call,调用Server.call方法,搜集结果并串行化,然后将结果放入Responder队列中。对于处理完的请求,需要将结果写回去,同样,利用NIO,只需要一个线程,相关的逻辑在Responder里。(摘自http://caibinbupt.iteye.com/blog/281281)
4.启动trash线程
startTrashEmptier
private void startTrashEmptier(Configuration conf) throws IOException {
this.emptier = new Thread(new Trash(conf).getEmptier(), "Trash Emptier");
this.emptier.setDaemon(true);
this.emptier.start();
}
主要就是删除旧的checkpoint,比较简单