本文以hadoop2.7.1的源码分析(主要是最新版本考虑更多因素,源码不够纯粹)
集群启动脚本分析
首先我们从启动hadoop集群说起,我们一般在单点hadoop启动集群一般直接使用 sbin/start-all.sh 或 sbin/stop-all.sh ,我们直接看启动脚本:
bin=`dirname "${BASH_SOURCE-$0}"`
bin=`cd "$bin"; pwd`
DEFAULT_LIBEXEC_DIR="$bin"/../libexec
HADOOP_LIBEXEC_DIR=${HADOOP_LIBEXEC_DIR:-$DEFAULT_LIBEXEC_DIR}
. $HADOOP_LIBEXEC_DIR/hadoop-config.sh
# start hdfs daemons if hdfs is present
if [ -f "${HADOOP_HDFS_HOME}"/sbin/start-dfs.sh ]; then
"${HADOOP_HDFS_HOME}"/sbin/start-dfs.sh --config $HADOOP_CONF_DIR
fi
# start yarn daemons if yarn is present
if [ -f "${HADOOP_YARN_HOME}"/sbin/start-yarn.sh ]; then
"${HADOOP_YARN_HOME}"/sbin/start-yarn.sh --config $HADOOP_CONF_DIR
fi
可以看出先后执行启动了 hadoop-config.sh
、 start-dfs.sh
和 start-yarn.sh
。由于本文主要探讨Yarn启动过程,所以我们直接看 start-yarn.sh
echo "starting yarn daemons"
bin=`dirname "${BASH_SOURCE-$0}"`
bin=`cd "$bin"; pwd`
DEFAULT_LIBEXEC_DIR="$bin"/../libexec
HADOOP_LIBEXEC_DIR=${HADOOP_LIBEXEC_DIR:-$DEFAULT_LIBEXEC_DIR}
. $HADOOP_LIBEXEC_DIR/yarn-config.sh
# start resourceManager
"$bin"/yarn-daemon.sh --config $YARN_CONF_DIR start resourcemanager
# start nodeManager
"$bin"/yarn-daemons.sh --config $YARN_CONF_DIR start nodemanager
# start proxyserver
#"$bin"/yarn-daemon.sh --config $YARN_CONF_DIR start proxyserver
可以看出Yarn先后调用 yarn-daemon.sh 启动 resourcemanager
组件;调用 yarn-daemons.sh 启动 nodemanager
组件,只不过 yarn-daemons.sh
也会调用 slaves.sh
后同样也会调用 yarn-daemon.sh
,所以我们继续分析 yarn-daemon.sh 脚本,我们看关键代码(如需看完整代码,点链接查阅即可):
...
echo starting $command, logging to $log
cd "$HADOOP_YARN_HOME"
nohup nice -n $YARN_NICENESS "$HADOOP_YARN_HOME"/bin/yarn --config $YARN_CONF_DIR $command "$@" > "$log" 2>&1 < /dev/null &
echo $! > $pid
...
可以看出最终调用 yarn 脚本,由于代码比较多,我们直接看启动 resourcemanager
和 nodemanager
组件的代码部分:
从脚本可以看出,会分别调用 org.apache.hadoop.yarn.server.resourcemanager.ResourceManager
和 org.apache.hadoop.yarn.server.nodemanager.NodeManager
类,调用 exec "$JAVA" -Dproc_$COMMAND $JAVA_HEAP_MAX $YARN_OPTS -classpath "$CLASSPATH" $CLASS "$@"
启动JVM进程启动常驻服务,即 ResourceManager
和 NodeManager
是入口类,我们以 ResourceManager
组件,继续往下分析…
注:上面介绍的一般在单节点上启动,但是在分布式情况,ResourceManager和NodeManager等分布在不同机器上,启动的进程不相同,
因此 推荐在不同节点分别启动组件 ,可以参考:
sbin/hadoop-daemons.sh start namenode
单独启动NameNode守护进程sbin/hadoop-daemons.sh start datanode
单独启动DataNode守护进程sbin/hadoop-daemons.sh start secondarynamenode
单独启动SecondaryNameNode守护进程sbin/yarn-daemon.sh start resourcemanager
单独启动ResourceManager守护进程sbin/yarn-daemon.sh start nodemanager
单独启动NodeManager守护进程
ResourceManager启动流程
首先我们先看下ResourceManager中的功能模块,有利于宏观掌握。ResourceManager在底层代码实现上将各个功能模块分的比较细,各个模块功能具有很强的独立性。如下图:
从上面启动脚本可以看出, org.apache.hadoop.yarn.server.resourcemanager.ResourceManager 是入口类的 main
函数。
其中重点需要分析的就是上面中的 resourceManager.init(conf);
和 resourceManager.start();
,下面分别介绍
初始化流程
如上面的代码中的 resourceManager.init(conf);
,首先调用父类 AbstractService 中 init
函数
从代码,可以其服务初始化实现在 ResourceManager
类的 serviceInit
函数,如下
setupDispatcher()
设置的是 Always On 服务,即不考虑HA状态的一直运行的服务。其初始化的 AsyncDispatcher
内部是 有一个 阻塞的事件队列,有一个一直运行的执行线程,当阻塞队列中有事件被放入,执行线程会把事件取出来,并获取事件的类型,从事件注册器 Map<Class<? extends Enum>, EventHandler>
中获取到对应的 EventHandler 对象,并调用该对象的 dispatch 方法分发事件。这样就完成了一次异步事件调用。
createAndInitActiveServices();
设置的是活动的服务上下文,即需要运行在Active RM节点上的服务,由于本文不重点解析,仅列出启动服务供参考:
- RMSecretManagerService
- ContainerAllocationExpirer
- AMLivelinessMonitor
- RMNodeLabelsManager
- RMStateStore
- RMApplicationHistoryWriter
- SystemMetricsPublisher
- NodesListManager
- ResourceScheduler
- SchedulerEventDispatcher
- NMLivelinessMonitor
- ResourceTrackerService
- ApplicationMasterService
- ClientRMService
- ApplicationMasterLauncher
- DelegationTokenRenewer
至此,配置加载和子服务初始化工作分析完了,下面我们分析,RM的启动流程
启动流程
我们回到ResourceManager类的 resourceManager.start();
继续分析,同样首先调用父类 AbstractService 中 start
函数
从代码,可以其服务初始化实现在 ResourceManager
类的 serviceStart
函数,如下
继续调用父类 CompositeService 的 serviceStart
函数
循环启动之前启动的每个服务。
自此整个Yarn的启动过程就分析完了…
中央事件调度器-AsyncDispatcher
由于AsyncDispatcher作为Yarn的一个重要的启动常驻服务,所以我们在此分析一下。
我们都知道,Yarn是采用了基于事件驱动的并发模型:
- 所有状态机都实现了EventHandler接口,很多服务(类名通常带有Service后缀)也实现了该接口,它们都是事件处理器。
- 需要异步处理的事件由中央异步调度器(类名通常带有Dispatcher后缀)统一接收/派发,需要同步处理的事件直接交给相应的事件处理器。
事件调度就是基于 AsyncDispatcher 实现的,由上面代码分析可得,AsyncDispatcher也是在yarn启动过程中 resourceManager.init(conf);
做为服务初始化的一员,最后在 resourceManager.start();
分别启动的,因此其启动最终也是调用 org.apache.hadoop.yarn.event.AsyncDispatcher
的函数 serviceStart
启动:
从代码可以看出,其实例化了一个子线程,用于异步处理事件,我们继续跟进代码,如下:
可以看出,在线程中循环从阻塞队列中拿出一个事件,然后分发事件处理,此过程相当于“生产者和消费者模型”中的消费者。而生产者则是由Client通过RPC提交给Yarn后,add到该阻塞队列,本文不再详述这部分。
具体的分发原理可以参考:Yarn的事件驱动模型与状态机