本篇内容关注NameNode启动之前,active状态和standby状态的一些后台服务及准备工作,即源码里的CommonServices。主要包括磁盘空间检查、
可用资源检查、安全模式等。依然分为三部分:源码调用分析、伪代码核心梳理、调用关系图解。
第一部分,源码调用分析。
接着上篇RpcServer启动之后开始梳理,进入到了initialize()方法中。
protected void initialize(Configuration conf) throws IOException {
// 可以通过找到下面变量名的映射,在hdfs-default.xml中找到对应的配置
if (conf.get(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS) == null) {
String intervals = conf.get(DFS_METRICS_PERCENTILES_INTERVALS_KEY);
if (intervals != null) {
conf.set(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS,
intervals);
}
}
...... // 核心代码:启动HttpServer
if (NamenodeRole.NAMENODE == role) {
startHttpServer(conf);
} this.spanReceiverHost = SpanReceiverHost.getInstance(conf); // 核心代码:FSNamesystem初始化
loadNamesystem(conf); // 核心代码:创建一个rpc server实例
rpcServer = createRpcServer(conf); ......
// 核心代码:启动一些服务组件,包括rpc server等
startCommonServices(conf);
}
本篇的核心代码,在最后一行的startCommonServices(),我们进去看一下都start了哪些service。
/**
* Start services common to both active and standby states
*/
void startCommonServices(Configuration conf, HAContext haContext) throws IOException {
......
try {
// 创建了一个NameNodeResourceChecker对象,用来检查namenode所在机器上的磁盘空间是否足够
nnResourceChecker = new NameNodeResourceChecker(conf); // 检查可用资源是否足够:如果不够,日志打印警告信息,然后进入安全模式
checkAvailableResources(); // 判断是否进入安全模式(安全模式是否实例化),并且副本队列是否应该被同步/复制(populate怎么翻译?)
assert safeMode != null && !isPopulatingReplQueues(); // 目前NameNode启动,进入到safemode阶段,处于一个等待汇报blocks的状态
StartupProgress prog = NameNode.getStartupProgress();
prog.beginPhase(Phase.SAFEMODE);
prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS,
getCompleteBlocksTotal()); // 设置block数量
setBlockTotal(); // 启动BlockManager里面的一堆后台线程
blockManager.activate(conf);
} finally {
writeUnlock();
}
......
}
这段代码里加粗的就是主要的核心逻辑,一步一步按顺序梳理。先从第一段核心代码,即new NameNodeResourceChecker()来看,这行代码实例化了
一个NameNodeResourceChecker对象,这个NameNodeResourceChecker类从名字猜测,大概是实现一个资源检查的功能。我们点进去验证
一下我们的猜想:
/**
*
* NameNodeResourceChecker provides a method -
* <code>hasAvailableDiskSpace</code> - which will return true if and only if
* the NameNode has disk space available on all required volumes, and any volume
* which is configured to be redundant. Volumes containing file system edits dirs
* are added by default, and arbitrary extra volumes may be configured as well.
*
* 提供了一个hasAvailableDiskSpace()方法,
* 这个方法仅仅在NameNode有足够的磁盘空间满足所有磁盘使用需求的时候
* 返回true,包括文件系统的edits dir,包括配置文件内设置的dirs。
* 否则,返回false.
*/
@InterfaceAudience.Private
public class NameNodeResourceChecker {
...... // Space (in bytes) reserved per volume.
// 配置文件内的每个被配置的磁盘路径空间的默认大小
private final long duReserved;
// 一个map,用来存放所有应该检查的配置路径,在NameNodeResourceChecker实例化的时候进行初始化
private Map<String, CheckedVolume> volumes;
...... @VisibleForTesting
class CheckedVolume implements CheckableNameNodeResource {
...... @Override
public boolean isResourceAvailable() {
long availableSpace = df.getAvailable();
...... // duReserved就是默认配置好的最小需要的磁盘空间
// duReserved空间默认是100Mb,默认情况下edits目录起码要有100Mb空间来写入日志,
// 否则就是检查资源失败
if (availableSpace < duReserved) {
LOG.warn("Space available on volume '" + volume + "' is "
+ availableSpace +
", which is below the configured reserved amount " + duReserved);
return false;
} else {
return true;
}
}
......
} /**
* Create a NameNodeResourceChecker, which will check the edits dirs and any
* additional dirs to check set in <code>conf</code>.
*
* 构造方法。
* 创建一个NameNodeResourceChecker对象,初始化volumes Map,用来存放需要进行磁盘空间检查的路径。
* 通过将这个volumes的map传递给NameNodeResourcePolicy类进行遍历volumes Map来进行检查edits dirs
* 和一些配置文件中配置的dirs。
*/
public NameNodeResourceChecker(Configuration conf) throws IOException {
this.conf = conf;
volumes = new HashMap<String, CheckedVolume>();
...... // 从配置文件中获取DFS_NAMENODE_DU_RESERVED_KEY配置
duReserved = conf.getLong(DFSConfigKeys.DFS_NAMENODE_DU_RESERVED_KEY,
DFSConfigKeys.DFS_NAMENODE_DU_RESERVED_DEFAULT);
......
} /**
* Add the volume of the passed-in directory to the list of volumes to check.
* If <code>required</code> is true, and this volume is already present, but
* is marked redundant, it will be marked required. If the volume is already
* present but marked required then this method is a no-op.
*
* @param directoryToCheck
* The directory whose volume will be checked for available space.
*/
private void addDirToCheck(URI directoryToCheck, boolean required)
throws IOException {
......
CheckedVolume newVolume = new CheckedVolume(dir, required);
CheckedVolume volume = volumes.get(newVolume.getVolume());
if(volume == null || !volume.isRequired()){
volumes.put(newVolume.getVolume(), newVolume);
}
} /**
* Return true if disk space is available on at least one of the configured
* redundant volumes, and all of the configured required volumes.
*
* @return True if the configured amount of disk space is available on at
* least one redundant volume and all of the required volumes, false
* otherwise.
* 如果磁盘空间满足所有配置要求的大小并且至少满足一个配置的冗余大小就返回true
*/
public boolean hasAvailableDiskSpace() {
return NameNodeResourcePolicy.areResourcesAvailable(volumes.values(),
minimumRedundantVolumes);
}
......
}
从NameNodeResourceChecker这个类的注释上可以清楚的得知,就是为了检查配置里的路径的磁盘空间大小是否满足使用需求的。它最核心的
两个变量一个是duReserved,一个是volumes。下面一一来分析:
duReserved, 这是一个long型变量,通过duReserved = conf.getLong(DFSConfigKeys.DFS_NAMENODE_DU_RESERVED_KEY,...);这行代码,
可以溯源到hdfs-default.xml中的dfs.namenode.resource.du.reserved这个配置选项,配置文件中默认为空,但是getLong()方法有一个默认大小,
是100M。那些待检查空间大小的路径首先会同这个duReserved进行比较,小于duReserved的话,返回false。最终会导致的结果是什么呢?先打个
疑问,一会儿会根据梳理的代码进行解疑。
volumes, 这是一个map,通过addDirToCheck()方法及注释可知,主要是用来存放需要进行磁盘空间检查的dirs。然后在hasAvailableDiskSpace()
中将这个map传入到了真正负责磁盘检查逻辑的类:NameNodeResourcePolicy,这个类就是用来遍历volumes这个map进行资源检查的。
前面提出的一个疑问:返回false结果会导致什么情况出现呢?通过跟踪hasAvailableDiskSpace()方法的被调用代码,一步一步进行梳理,最终会
进入到FSNamesystem类的一个内部线程类中:
/**
* Periodically calls hasAvailableResources of NameNodeResourceChecker, and if
* there are found to be insufficient resources available, causes the NN to
* enter SAFE MODE. If resources are later found to have returned to
* acceptable levels, this daemon will cause the NN to exit safe mode.
*/
class NameNodeResourceMonitor implements Runnable {
boolean shouldNNRmRun = true;
@Override
public void run () {
try {
while (fsRunning && shouldNNRmRun) {
checkAvailableResources();
if(!nameNodeHasResourcesAvailable()) {
String lowResourcesMsg = "NameNode low on available disk space. ";
if (!isInSafeMode()) {
FSNamesystem.LOG.warn(lowResourcesMsg + "Entering safe mode.");
} else {
FSNamesystem.LOG.warn(lowResourcesMsg + "Already in safe mode.");
}
// 进入到安全模式
enterSafeMode(true);
}
......
}
} catch (Exception e) {
FSNamesystem.LOG.error("Exception in NameNodeResourceMonitor: ", e);
}
}
注释告诉我们,这个线程会周期性的调用NameNodeResourceChecker的hasAvailableRecourses()方法进行磁盘资源检查,如果一旦发现有资源
不足的情况,会使NameNode进入安全模式。如果随后返回的状态代表资源大小到达可使用的级别,那么这个线程就使NameNode退出安全模式。
依照这个注释,去解读run()方法的代码逻辑:在一个while循环里,首先判断资源是否可用,如果不可用,日志里就会发出一个警告信息,然后调用
enterSafeMode();进入安全模式。
为什么磁盘资源不足要进入安全模式呢?很简单,磁盘资源不足的情况下,任何对元数据修改所产生的日志都无法确保能够写入到磁盘,即新产生的
edits log和fsimage都无法确保写入磁盘。所以要进入安全模式,来禁止元数据的变动以避免往磁盘写入新的日志数据。
那么具体的进入安全模式方法里都有哪些操作,可以自己进去enterSafeMode()方法中看一下。代码就不在这儿贴出来了,主要涉及到一些日志文件的
同步,然后在同步完成之后进行加锁处理禁止所有写的操作。
回到startCommonServices()中来,继续下一个核心逻辑的分析,isPopulatingReplQueres();
/**
* 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;
} private boolean shouldPopulateReplQueues() {
if(haContext == null || haContext.getState() == null)
return false;
return haContext.getState().shouldPopulateReplQueues();
}
这个方法是用来检查副本队列是否应该被同步/复制(populate应该怎么翻译啊?大概就是这么个意思吧),返回的是副本队列是否已经初始化的状态。
继续往下看,NameNode.getStartupProgress()这行代码会获取一个StarupProgress实例,这个类用来指示NameNode进程内各项任务的启动情况和
进度的,比如一个任务某个阶段的开始和结束信息等。并且是线程安全的。
第二部分,伪代码核心梳理。
NameNode.main() // 入口函数
|——createNameNode(); // 通过new NameNode()进行实例化
|——initialize(); // 方法进行初始化操作
|——startHttpServer(); // 启动HttpServer
|——loadNamesystem(); // 加载元数据
|——createRpcServer(); // 创建并初始化rpc server实例
|——startCommonServices();
|——namesystem.startCommonServices(); // 启动一些磁盘检查、安全模式等一些后台服务及线程
|——new NameNodeResourceChecker(); // 实例化一个NameNodeResourceChecker并准备出所有需要检查的磁盘路径
|——checkAvailableResources(); // 开始磁盘空间检查
|——NameNode.getStartupProgress(); // 获取StartupProgress实例用来获取NameNode各任务的启动信息
|——setBlockTotal(); // 设置所有的block,用于后面判断是否进入安全模式
|——blockManager.activate(); // 启动BlockManager里面的一堆关于block副本处理的后台线程
|——rpcServer.start(); // 启动rpcServer
|——join()
第三部分,调用关系图解。
这篇内容到这儿就完事儿了,可能有些地方分析的不够深入,但是在确定的场景下,梳理主要的骨干代码,更有助于理解和把握整个场景的流程。
用一个大师的说法叫抓大放小,即从宏观上把握主要梗概,放弃一些无关紧要的次要枝丫。
做一下预告,下一篇内容将是NameNode启动的这个场景最后一部分内容:blockManager后台线程。
Hadoop源码学习笔记之NameNode启动场景流程五:磁盘空间检查及安全模式检查的更多相关文章
-
Hadoop源码学习笔记之NameNode启动场景流程四:rpc server初始化及启动
老规矩,还是分三步走,分别为源码调用分析.伪代码核心梳理.调用关系图解. 一.源码调用分析 根据上篇的梳理,直接从initialize()方法着手.源码如下,部分代码的功能以及说明,已经在注释阐述了. ...
-
Hadoop源码学习笔记之NameNode启动场景流程二:http server启动源码剖析
NameNodeHttpServer启动源码剖析,这一部分主要按以下步骤进行: 一.源码调用分析 二.伪代码调用流程梳理 三.http server服务流程图解 第一步,源码调用分析 前一篇文章已经锁 ...
-
Hadoop源码学习笔记之NameNode启动场景流程三:FSNamesystem初始化源码剖析
上篇内容分析了http server的启动代码,这篇文章继续从initialize()方法中按执行顺序进行分析.内容还是分为三大块: 一.源码调用关系分析 二.伪代码执行流程 三.代码图解 一.源码调 ...
-
Hadoop源码学习笔记之NameNode启动场景流程一:源码环境搭建和项目模块及NameNode结构简单介绍
最近在跟着一个大佬学习Hadoop底层源码及架构等知识点,觉得有必要记录下来这个学习过程.想到了这个废弃已久的blog账号,决定重新开始更新. 主要分以下几步来进行源码学习: 一.搭建源码阅读环境二. ...
-
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...
-
Hadoop源码学习笔记(6)——从ls命令一路解剖
Hadoop源码学习笔记(6) ——从ls命令一路解剖 Hadoop几个模块的程序我们大致有了点了解,现在我们得细看一下这个程序是如何处理命令的. 我们就从原头开始,然后一步步追查. 我们先选中ls命 ...
-
Hadoop源码学习笔记(2) ——进入main函数打印包信息
Hadoop源码学习笔记(2) ——进入main函数打印包信息 找到了main函数,也建立了快速启动的方法,然后我们就进去看一看. 进入NameNode和DataNode的主函数后,发现形式差不多: ...
-
Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类
Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何 ...
-
Hadoop源码学习笔记(4) ——Socket到RPC调用
Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...
随机推荐
-
jQuery treetable【表格多重折叠树功能及拖放表格子元素重新排列】
今天有个表格需求做到多重折叠子元素功能,仔细想了下实现原理, 1.在html中,把父子节点的关系写在自定义属性,但对于节点是否有孩子(hasChild),是否是最后一个节点(isLastOne),是否 ...
-
BZOJ1766 : [Ceoi2009]photo
如果两个矩形相交且不是包含关系,那么完全可以让它们不相交. 将坐标离散化后,设$f[i][j][k]$表示区间$[i,j]$纵坐标不小于$k$的部分的最优解. 对于$f[i][j][k]$,要么枚举分 ...
-
java 取小数点后两位 不四舍五入,怎么做
java 取小数点后两位 不四舍五入,怎么做 正常版: //正常版: import java.text.DecimalFormat; import java.math.RoundingMode; De ...
-
程序员的自我救赎---10.1:APP版本控制系统
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
-
nginx 出现504 Gateway Time-out的解决方法
本文介绍nginx出现504 Gateway Time-out问题的原因,分析问题并提供解决方法. 1.问题分析nginx访问出现504 Gateway Time-out,一般是由于程序执行时间过长导 ...
-
Hadoop源码阅读环境搭建(IDEA)
拿到一份Hadoop源码之后,经常关注的两件事情就是 1.怎么阅读?涉及IDEA和Eclipse工程搭建.IDEA搭建,选择源码,逐步导入即可:Eclipse可以选择后台生成工程,也可以选择IDE导入 ...
-
Java虚拟机性能监控与调优
1 基于JDK命令行工具的监控 1.1 JVM的参数类型 1.1.1 标准参数 在JVM的各个版本基本上保持不变,很稳定的. -help -server -client -version -showv ...
-
java工具类之按对象中某属性排序
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang ...
-
如何安装Zend Studio 以及汉化和基本准备工作
昨天从早上一直弄到晚上10点,可累死我了,网上的资料都是掺次不齐,所以我写一篇系统点的文章来告诉大家怎么做. 1.如果你想进行一套PHP系统的开发,肯定是要有“尚方宝剑”的,这个尚方宝剑就是PHP工具 ...
-
.net 任务(Task)
1. Task (任务): 很容易调用 ThreadPool.QueueUserWorkItem 实现异步操作,但是这个技术有许多 .net 引入Task类型来使用任务. 如下几种方式都是实现异步的方 ...