008-hadoop二次开发-NameNode启动流程
在源码文件FSNamesystem.java执行完nnResourceChecker = new NameNodeResourceChecker(conf);
立马执行checkAvailableResources(),检查可用资源是否足够:如果不够,日志打印警告信息,然后进入安全模式。
然后
/**
* 磁盘资源不足的情况下,任何对元数据修改所产生的日志都无法确保能够写入到磁盘,
* 即新产生的edits log和fsimage都无法确保写入磁盘。所以要进入安全模式,
* 来禁止元数据的变动以避免往磁盘写入新的日志数据
* */
assert safeMode != null && !isPopulatingReplQueues();
/**
* Check if replication queues are to be populated
* @return true when node is HAState.Active and not in the very first safemode
*/
/**这个方法是用来检查副本队列是否应该被同步/复制*/
@Override
public boolean isPopulatingReplQueues() {
if (!shouldPopulateReplQueues()) {
return false;
}
return initializedReplQueues;
}
设置数据块汇报
//处于一个等待汇报blocks的状态
prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS, getCompleteBlocksTotal());
/**
* NN端的block有四种状态:
* 1、UnderConstruction(正在被写入的状态)
* 2、UnderRecovery(正在被恢复的块)
* 3、Committed(已经确定好它的字节大小与generation stamp值(可理解为版本号))
* 4、Complete(写入执行操作结束状态)
* 启动的时候 ,namenode认为只有block状态为Complete,才会被读取
*
* 获取系统中的COMPLETE块总数。
* 对于安全模式,仅计算完整块。
*/
private long getCompleteBlocksTotal() {
// Calculate number of blocks under construction
long numUCBlocks = 0;
readLock();
//获取所有正在构建的block块
//leaseManager 文件租约(为每一个客户端和文件构建关联)
numUCBlocks = leaseManager.getNumUnderConstructionBlocks();
try {
return getBlocksTotal() - numUCBlocks;
} finally {
readUnlock();
}
}
设置所有的block,用于后面判断是否进入安全模式setBlockTotal(),
/**
* Set the total number of blocks in the system.
*/
public void setBlockTotal() {
// safeMode is volatile, and may be set to null at any time
SafeModeInfo safeMode = this.safeMode;
if (safeMode == null)
return;
//TODO 设置安全模式
//getCompleteBlocksTotal 获取所有正常使用的block个数
safeMode.setBlockTotal((int)getCompleteBlocksTotal());
}
通过SafeModeInfo safeMode = this.safeMode;拿到状态信息,
/** Time when threshold was reached.
* <br> -1 safe mode is off
* <br> 0 safe mode is on, and threshold is not reached yet
* <br> >0 safe mode is on, but we are in extension period
*/
private long reached = -1;
/**
* 1、得到满足安全模式阈值条件所需的块数
* 2、填充复制队列之前所需的块数
* 3、检查是否需要进入安全模式
*/
private synchronized void setBlockTotal(int total) {
this.blockTotal = total;//汇报过来的blocks(complete状态)个数
//满足安全模式阈值条件所需的块数 blockTotal * 0.999f 1000 * 0.999 = 999
this.blockThreshold = (int) (blockTotal * threshold);
//填充复制队列之前所需的块数 blockTotal * 0.999f
this.blockReplQueueThreshold = (int) (blockTotal * replQueueThreshold);
if (haEnabled) {
// After we initialize the block count, any further namespace
// modifications done while in safe mode need to keep track
// of the number of total blocks in the system.
this.shouldIncrementallyTrackBlocks = true;
}
/**
* blockSafe是datanode向namenode进行汇报的块个数,通过incrementSafeBlockCount方法,不断的叠加起来的
* 通过decrementSafeBlockCount,当datanode向namenode汇报删除数据块的时候,此处就对blockSafe减小
* */
if(blockSafe < 0)
this.blockSafe = 0;
checkMode();
}
/**
* 用于检查安全模式的状态:
* 1、判断阈值系数是否满足进入安全模式:needEnter
* 对于离开安全模式,有两个条件判断:
* 1、判断系数是否满足离开安全模式
* 2、启动SafeModeMonitor线程,每隔1秒去查看下,是否可以退出安全模式
*/
private void checkMode() {
/**assert 如果<boolean表达式>为true,则程序继续执行。
如果为false,则程序抛出AssertionError,并终止执行。*/
assert hasWriteLock();//安全模式下,需要写锁,禁止写入
//如果当前节点已经是active状态,则不需要在检查了,直接返回
if (inTransitionToActive()) {
return;
}
//TODO 判断是否进入安全模式
if (smmthread == null && needEnter()) {
//进入安全模式
enter();
/**
* canInitializeReplQueues:判断namenode已经接收到的blockSafe块数量是否达到了
* 恢复复制和删除数据块数据块数量
* */
if (canInitializeReplQueues() && !isPopulatingReplQueues()
&& !haEnabled) {
initializeReplQueues();
}
reportStatus("STATE* Safe mode ON.", false);
return;
}
/**
* extension处于安全模式的时间(满足了退出安全模式条件 ,此时还是处于安全模式的时间)
* threshold安全系数
* isOn 当前的状态(reached)是否小于0
* */
if (!isOn() || extension <= 0 || threshold <= 0) { // don't need to wait
this.leave(); // 退出安全模式
return;
}
if (reached > 0) { // threshold has already been reached before
reportStatus("STATE* Safe mode ON.", false);
return;
}
/**TODO 启动SafeModeMonitor线程,每隔1秒去查看下,是否可以退出安全模式*/
reached = monotonicNow();
reachedTimestamp = now();
if (smmthread == null) {
smmthread = new Daemon(new SafeModeMonitor());
smmthread.start();
reportStatus("STATE* Safe mode extension entered.", true);
}
// check if we are ready to initialize replication queues
if (canInitializeReplQueues() && !isPopulatingReplQueues() && !haEnabled) {
initializeReplQueues();
}
}
是否进入安全模式的enter方法
/**
* Enter safe mode.
*/
private void enter() {
/**
* reached
* <br> -1 退出安全模式
* <br> 0 进入安全模式
* */
this.reached = 0;
this.reachedTimestamp = 0;
}
怎么进入安全模式?
首先守护线程smmthread为空,并且needEnter()为真,
/**
* There is no need to enter safe mode
* if DFS is empty or {@link #threshold} == 0
*/
/**
* 进入安全模式有3个条件,任何一个满足,都会进入安全模式
* 条件1:threshold != 0 && blockSafe < blockThreshold)
* this.blockThreshold = (int) (blockTotal * threshold);
* 也就是说:如果有1000个block,那么阈值blockThreshold=999
* 但是如果集群启动,datanode汇报过来的累计的块数小于<999,那么就会进入安全模式
* 条件2:datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold
* 启动集群后,存活的datanode个数小于datanodeThreshold(默认0),则进入安全模式
*
* 条件3:!nameNodeHasResourcesAvailable()
* 检查磁盘,是否大于100M
*
* */
private boolean needEnter() {
//假如1000个数据块 , blockSafe < 999
return (threshold != 0 && blockSafe < blockThreshold) ||
(datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold) ||
(!nameNodeHasResourcesAvailable());
}
是否离开安全模式中的isOn方法
/**
* Check if safe mode is on.
* @return true if in safe mode
*/
private synchronized boolean isOn() {
doConsistencyCheck();
//安全模式化 reached = 0
//非 reached = -1
return this.reached >= 0;
}
接下来创建守护线程
进入SafeModeMonitor线程,实现了Runnable接口,在run方法中每个1s钟循环检查是否要退出安全模式,整体加了写锁,最后释放写锁。
@Override
public void run() {
while (fsRunning) {
writeLock();
try {
if (safeMode == null) { // Not in safe mode.
break;
}
//TODO 判断是否满足退出安全模式
if (safeMode.canLeave()) {
// Leave safe mode.
safeMode.leave();//方法中将 reached = -1;
smmthread = null;//将守护线程置空
break;
}
} finally {
writeUnlock();
}
try {
Thread.sleep(recheckInterval);//TODO 每隔1s检查一遍是否满足条件
} catch (InterruptedException ie) {
// Ignored
}
}
if (!fsRunning) {
LOG.info("NameNode is being shutdown, exit SafeModeMonitor thread");
}
}
}
若退出安全模式,需满足safeMode.canLeave()为true,
/**
* 通过3个条件来判断是否可以离开安全模式:
* 1、reached是否为-1
* 2、在满足最小副本条件之后,namenode还需要处于安全模式的时间(30s)
* 3、needEnter里面的3个条件
* */
private synchronized boolean canLeave() {
//reached == -1是离开safemode
if (reached == 0) {
return false;
}
//extension 默认30s,也就是满足最低副本系数之后,离开安全模式的时间,这个时间用于等待剩余数据节点的数据块上报
//这里的reached是来自于创建守护线程前reached = monotonicNow();记录一个当前时间
//此处的monotonicNow()又拿到一个当前时间
if (monotonicNow() - reached < extension) {
reportStatus("STATE* Safe mode ON, in safe mode extension.", false);
return false;
}
if (needEnter()) {
reportStatus("STATE* Safe mode ON, thresholds not met.", false);
return false;
}
return true;
}
monotonicNow()保障时间的准确性
public static long monotonicNow() {
final long NANOSECONDS_PER_MILLISECOND = 1000000;
return System.nanoTime() / NANOSECONDS_PER_MILLISECOND;
}
最后
//TODO 启动BlockManager里面关于block副本处理的后台线程
//激活BlockManager
blockManager.activate(conf);