(二)HBase集群启动流程分析

在安装HBase的时候需要配置一些参数,这些参数在HBase启动的时候发挥着怎样的作用,如何影响的HBase的运行,出现启动错误时如何快速定位错误的位置。在解决这些问题之前,先从源码的角度分析一下HBase的启动流程,了解HBase是怎么启动的。

启动流程概述

启动HBase会执行start-hbase.sh,然后脚本会先执行hbase-config.sh,做一系列的配置设置,包括常用路径、regionservers和backup-masters列表、常用端口等。在hbase-config.sh中会执行hbase-env.sh,主要对JAVA的环境参数、ssh,、pid路径等进行配置。start-hbase.sh最后会根据hbase.cluster.distributed来确定启动模式,分为本地和分布式。
分布式启动HBase,使用hbase-daemons.sh逐步启动zookeeper、master、regionserver、master-backup。具体会调用相关的脚本(如zookeeper会调用zookeerper.sh)来进行环境的配置和登录到相关的机器节点上执行hbase-daemon.sh。
hbase-daemon.sh的职责就是启动各个进程,在启动过程中会先做进程判断,日志滚动等准备,最后执行启动命名,逐步的启动各个节点上的进程。在启动过程中,会在屏幕中打印启动信息。
执行流程图:

具体启动流程

接下来分析每一步的源码,了解整个执行的过程。

start-hbase.sh

作为HBase启动的入口,在执行hbase-config.sh设置完运行的环境变量后,会执行命令,获取hbase.cluster.distributed的值来确定HBase的启动模式。

1
distMode=`$bin/hbase --config "$HBASE_CONF_DIR" org.apache.hadoop.hbase.util.HBaseConfTool hbase.cluster.distributed | head -n 1`

具体的代码实现:

hbase.cluster.distributed在hbase-site.xml中进行配置,默认值为false。false将启动standalone模式,true将启动distributed模式,在配置分布式的时候需要改成true。

1
2
3
4
5
6
7
8
9
10
11
if [ "$distMode" == 'false' ]
then
"$bin"/hbase-daemon.sh --config "${HBASE_CONF_DIR}" $commandToRun master $@
else
"$bin"/hbase-daemons.sh --config "${HBASE_CONF_DIR}" $commandToRun zookeeper
"$bin"/hbase-daemon.sh --config "${HBASE_CONF_DIR}" $commandToRun master
"$bin"/hbase-daemons.sh --config "${HBASE_CONF_DIR}" \
--hosts "${HBASE_REGIONSERVERS}" $commandToRun regionserver
"$bin"/hbase-daemons.sh --config "${HBASE_CONF_DIR}" \
--hosts "${HBASE_BACKUP_MASTERS}" $commandToRun master-backup
fi

standalone模式将会在一个JVM中运行所有的HBase和zookeeper。distributed模式会逐渐启动zookeeper、master、regionserver、master-backup等进程和节点。在启动节点时,$commandToRun有两种选择,可以是start(默认),还可以是autorestart。

hbase-config.sh

由于启动的脚本如hbase-daemon.sh,zookeerper.sh等都不是在一台机器上运行,因此每个脚本在执行前都会先执行一下hbase-config.sh来配置运行时的环境变量。
在hbase-config.sh中首先会检查hbase-config.sh是否是一个软连接,找到hbase-config.sh的真实路径。然后设置HBASE_HOME,判断是否指定了hbase的配置文件路径,如果没有将采用默认的配置文件路径。
脚本会设置一些默认的参数,包括HBASE_CONF_DIR、HBASE_REGIONSERVERS、HBASE_BACKUP_MASTERS、HBASE_THRIFT_JMX_OPTS等,然后执行hbase-env.sh配置HBase的AVA的运行环境和ssh参数等。程序会判断是否设置HBASE_REGIONSERVER_MLOCK为true,这个主要是判断系统是否使用了mlock来锁住内存,防止这段内存被操作系统swap掉。这将阻止Linux 将这个内存页调度到交换空间(swap space),即使该程序已有一段时间没有访问这段空间。最后如果检测到了JAVA_HOME后,程序将会继续运行。

hbase-env.sh

hbase-env.sh中设置了HBase运行中的一些重要的JVM参数,后续在进行HBase调优时会用到这些参数。除此之外还有一些进程优先级,SSH参数等。主要参数如下:
1、JAVA_HOME
JAVA的JDK路径,需要java 1.7以上
2、HBASE_HEAPSIZE
堆的最大使用量。默认情况下是JVM默认值
3、HBASE_OFFHEAPSIZE
如果打算使用堆缓存,可以设置该值。例如,要分配8G的offheap,将值设置为“8G”。
4、HBASE_OPTS
默认为”-XX:+UseConcMarkSweepGC”
使用CMS收集器对年老代进行垃圾收集,CMS收集器通过多线程并发进行垃圾回收,尽量减少垃圾收集造成的停顿。
参考文档:Java内存与垃圾回收调优 - ImportNew
5、PermSize设置非堆内存初始值
仅仅是JDK7需要配置,JDK8+后就不需要
6、HBASE_MASTER_OPTS、HBASE_REGIONSERVER_OPTS
配置非堆内存,初始分配的堆内存,大允许分配的堆内存,按需分配
参考文档:Xms Xmx PermSize MaxPermSize 区别 - 残星 - 博客园
7、java回收机制,分别配置sever和client端
HBase会在启动的时候讲Java的一些配置打印到.out日志中

1
2
3
SERVER_GC_OPTS="-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
SERVER_GC_OPTS="-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:<FILE-PATH>"
SERVER_GC_OPTS="-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:<FILE-PATH> -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"

8、HBASE_MANAGES_ZK
是否由它自己的zookpeer来管理,一般使用单独的zookeeper集群来管理hbase。
9、HBASE_PID_DIR
pid的路径,默认_tmp目录下,但_tmp中的文件易失,导致找不到pid文件,最后配置一个其它稳定的文件。
10、HBASE_NICENESS
守护进程的调度优先级
11、HBASE_IDENT_STRING
标志hbase实例的字符串,默认情况下为当前用户,会在创建pid,log等文件时使用
12、HBASE_BACKUP_MASTERS
在backup-masters中配置配置bakcup的master,当一台master宕机后,zookeeper会在backup中选择一个。默认backup-masters是不存在的,需要自己新建
13、HBASE_SSH_OPTS
ssh配置,如果ssh的端口不是22,可以进行设置HBASE_SSH_OPTS=”-p xxx”,后面再启动各个进程的时候会用到,如:

1
ssh $HBASE_SSH_OPTS $zookeeper $cmd 2>&1 | sed "s/^/$zookeeper: /" &

14、HBASE_LOG_DIR
hbase的日志路径

hbase-daemons.sh

hbase-daemons.sh比较简单,主要根据要启动的进程,生成好远程执行命令remote_cmd,然后做分发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
remote_cmd="cd ${HBASE_HOME}; $bin/hbase-daemon.sh --config ${HBASE_CONF_DIR} $@"
args="--hosts ${HBASE_REGIONSERVERS} --config ${HBASE_CONF_DIR} $remote_cmd"
command=$2
case $command in
(zookeeper)
exec "$bin/zookeepers.sh" $args
;;
(master-backup)
exec "$bin/master-backup.sh" $args
;;
(*)
exec "$bin/regionservers.sh" $args
;;
esac

zookeepers.sh

如果HBASE_MANAGES_ZK为true,表示hbase使用自带的zookeeper集群,如果不是,需要去加载用户自定义的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if [ "$HBASE_MANAGES_ZK" = "true" ]; then
hosts=`"$bin"/hbase org.apache.hadoop.hbase.zookeeper.ZKServerTool | grep '^ZK host:' | sed 's,^ZK host:,,'`
# 获取执行命令,从hbase-daemons.sh传过来
# remote_cmd="cd ${HBASE_HOME}; $bin/hbase-daemon.sh --config ${HBASE_CONF_DIR} $@"
cmd=$"${@// /\\ }"
for zookeeper in $hosts; do
# 登录到节点上
ssh $HBASE_SSH_OPTS $zookeeper $cmd 2>&1 | sed "s/^/$zookeeper: /" &
if [ "$HBASE_SLAVE_SLEEP" != "" ]; then
# 等待
sleep $HBASE_SLAVE_SLEEP
fi
done
fi
wait

org.apache.hadoop.hbase.zookeeper.ZKServerTool中通过readZKNodes获取到zookeerper的节点地址。
其会在conf目录下搜寻zoo.cfg文件,加载zookeeper的配置,如果没有则会去hbase-site.xml中加载zookeeper的配置,包括hbase.zookeeper.quorum,hbase.zookeeper.property.clientPort,hbase.zookeeper.property.dataDir等参数。因此zookeeper的信息可以在zoo.cfg中配置,也可以在hbase-site.xml中进行配置。

master-backup.sh

在master-backup.sh中,首先会获取到 backup-masters列表,然后登陆到节点上去以backup的方式启动master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HOSTLIST=$HBASE_BACKUP_MASTERS
# 获取backup-masters列表,在hbase-env.sh中设置HBASE_BACKUP_MASTERS参数
if [ "$HOSTLIST" = "" ]; then
if [ "$HBASE_BACKUP_MASTERS" = "" ]; then
export HOSTLIST="${HBASE_CONF_DIR}/backup-masters"
else
export HOSTLIST="${HBASE_BACKUP_MASTERS}"
fi
fi
args=${@// /\\ }
args=${args/master-backup/master}
if [ -f $HOSTLIST ]; then
for hmaster in `cat "$HOSTLIST"`; do
# 登录到节点上 启动backup master
ssh $HBASE_SSH_OPTS $hmaster $"$args --backup" \
2>&1 | sed "s/^/$hmaster: /" &
if [ "$HBASE_SLAVE_SLEEP" != "" ]; then
sleep $HBASE_SLAVE_SLEEP
fi
done
fi

启动后zookeeper会自动选取一个master作为active,其它的都是backup。

regionservers.sh

首先获取到regionservers列表,默认在conf/regionservers中进行设置,如果regionservers是默认的localhost,则会在本地启动regionserver。设置成distributed后,会在各个节点上启动regionserver节点。$”${@// /\\ }“会将命令中将所有的\替换成为空格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
HOSTLIST=$HBASE_REGIONSERVERS
# 获取regionservers
if [ "$HOSTLIST" = "" ]; then
if [ "$HBASE_REGIONSERVERS" = "" ]; then
export HOSTLIST="${HBASE_CONF_DIR}/regionservers"
else
export HOSTLIST="${HBASE_REGIONSERVERS}"
fi
fi
regionservers=`cat "$HOSTLIST"`
# 本地模式
if [ "$regionservers" = "localhost" ]; then
"$bin"/local-regionservers.sh start 1
else
for regionserver in `cat "$HOSTLIST"`; do
# 并行执行
if ${HBASE_SLAVE_PARALLEL:-true}; then
ssh $HBASE_SSH_OPTS $regionserver $"${@// /\\ }" \
2>&1 | sed "s/^/$regionserver: /" &
else # run each command serially
ssh $HBASE_SSH_OPTS $regionserver $"${@// /\\ }" \
2>&1 | sed "s/^/$regionserver: /"
fi
if [ "$HBASE_SLAVE_SLEEP" != "" ]; then
sleep $HBASE_SLAVE_SLEEP
fi
done
fi

hbase-daemon.sh

前面的脚本都是做的准备工作,启动各个节点最后都是由hbase-daemon.sh来完成的。因此hbase-daemon.sh应该是最重要的脚本,负责启动前的环境清理,日志滚动以及进程启动等工作。
hbase-daemon.sh支持start|stop|restart|autorestart|foreground_start,5种启动方式,可以单独用来管理节点,如启动regionserver:hbase-daemon.sh start regionserver
经过一系列的日志文件路径设置后,开始执行start过程。

1
2
3
4
5
6
7
8
9
10
11
(start)
check_before_start
hbase_rotate_log $HBASE_LOGOUT
hbase_rotate_log $HBASE_LOGGC
# 启动进程
echo starting $command, logging to $HBASE_LOGOUT
$thiscmd --config "${HBASE_CONF_DIR}" \
foreground_start $command $args < /dev/null > ${HBASE_LOGOUT} 2>&1 &
disown -h -r
sleep 1; head "${HBASE_LOGOUT}"
;;

首先check_before_start检查要启动的进程是否存在,如果存在就会发出$command running as process cat $HBASE_PID. Stop it first的提示。看到这个提示,应该到该机器节点上去查询一下该进程的情况。

1
2
3
4
5
6
7
8
9
10
11
check_before_start(){
#ckeck if the process is not running
mkdir -p "$HBASE_PID_DIR"
if [ -f $HBASE_PID ]; then
# kill -0 pid 不发送任何信号,但是系统会进行错误检查,检查一个进程是否存在,存在返回0;不存在返回1
if kill -0 `cat $HBASE_PID` > /dev/null 2>&1; then
echo $command running as process `cat $HBASE_PID`. Stop it first.
exit 1
fi
fi
}

接着进行日志的滚动,如果日志文件存在就进行1->5的滚动,因此我们能够看到logs下有编号1到5的日志文件。默认情况是不会输出GC日志的,可以在hbase-env.sh中开启SERVER_GC_OPTS,CLIENT_GC_OPTS配置,才会输出GC日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
hbase_rotate_log ()
{
log=$1;
num=5;
if [ -n "$2" ]; then
num=$2
fi
# 检查日志文件是否存在,并做日志滚动
if [ -f "$log" ]; then # rotate logs
while [ $num -gt 1 ]; do
prev=`expr $num - 1`
[ -f "$log.$prev" ] && mv -f "$log.$prev" "$log.$num"
num=$prev
done
mv -f "$log" "$log.$num";
fi
}

随后会使用foreground_start来启动进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(foreground_start)
# 当脚本收到SIGHUP SIGINT SIGTERM EXIT的信号时,trap命令执行双引号中的命令
trap cleanAfterRun SIGHUP SIGINT SIGTERM EXIT
if [ "$HBASE_NO_REDIRECT_LOG" != "" ]; then
# NO REDIRECT
echo "`date` Starting $command on `hostname`"
echo "`ulimit -a`"
# in case the parent shell gets the kill make sure to trap signals.
# Only one will get called. Either the trap or the flow will go through.
# 设置hbase节点进程执行的优先级
nice -n $HBASE_NICENESS "$HBASE_HOME"/bin/hbase \
--config "${HBASE_CONF_DIR}" \
$command "$@" start &
else
echo "`date` Starting $command on `hostname`" >> ${HBASE_LOGLOG}
echo "`ulimit -a`" >> "$HBASE_LOGLOG" 2>&1
# in case the parent shell gets the kill make sure to trap signals.
# Only one will get called. Either the trap or the flow will go through.
nice -n $HBASE_NICENESS "$HBASE_HOME"/bin/hbase \
--config "${HBASE_CONF_DIR}" \
$command "$@" start >> ${HBASE_LOGOUT} 2>&1 &
fi
# Add to the command log file vital stats on our environment.
hbase_pid=$!
echo $hbase_pid > ${HBASE_PID}
wait $hbase_pid
;;

当脚本收到SIGHUP SIGINT SIGTERM EXIT的信号时,trap命令执行cleanAfterRun,kill掉已经存在的进程,然后告诉zk删除有问题的节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 当运行遇到问题时进行清理
cleanAfterRun() {
if [ -f ${HBASE_PID} ]; then
# If the process is still running time to tear it down.
kill -9 `cat ${HBASE_PID}` > /dev/null 2>&1
rm -f ${HBASE_PID} > /dev/null 2>&1
fi
if [ -f ${HBASE_ZNODE_FILE} ]; then
if [ "$command" = "master" ]; then
HBASE_OPTS="$HBASE_OPTS $HBASE_MASTER_OPTS" $bin/hbase master clear > /dev/null 2>&1
else
#call ZK to delete the node
# 告诉zk删除有问题的节点
ZNODE=`cat ${HBASE_ZNODE_FILE}`
HBASE_OPTS="$HBASE_OPTS $HBASE_REGIONSERVER_OPTS" $bin/hbase zkcli delete ${ZNODE} > /dev/null 2>&1
fi
rm ${HBASE_ZNODE_FILE}
fi
}

从代码中可以看到,最后启动脚本使用hbase命令来启动的,在hbase中将会进行一系列的设置如日志,lib库,执行类等,最终会运行exec "$JAVA" -Dproc_$COMMAND -XX:OnOutOfMemoryError="kill -9 %p" $HEAP_SETTINGS $HBASE_OPTS $CLASS "$@"来启动相应的进程。执行成功后,可以ps -ef|grep 进程名来查看最后是执行了什么命令来启动进程的,如查看HMaster如何启动的ps -ef|grep HMaster,就会列出具体的进程信息。

1
/usr/java/jdk1.8.0_65/bin/java -Dproc_master -XX:OnOutOfMemoryError=kill -9 %p -Xmx5G -XX:+UseConcMarkSweepGC -XX:PermSize=128m -XX:MaxPermSize=128m -Dhbase.log.dir=/home/xuxphp/hbase/logs -Dhbase.log.file=hbase-xuxphp-master-xuxp022.log -Dhbase.home.dir=/home/xuxphp/hbase -Dhbase.id.str=xuxphp -Dhbase.root.logger=INFO,RFA -Djava.library.path=/home/xuxphp/hadoop/lib/native -Dhbase.security.logger=INFO,RFAS org.apache.hadoop.hbase.master.HMaster start

总结

HBase在启动过程中,会先配置大量的参数,重要的有JVM参数,regionserver,master-backup列表,路径等。分析一遍启动的过程后,明白了哪些参数在什么位置使用,起到了什么样的作用,能够为HBase的运维提供一个指导。最后自己也学到了很多shell编程的知识。