引言
上一节中,我们手工编译了mysql5.7.28。这一节就从启动脚本mysqld_safe入手,继续介绍MySQL。用mysqld_safe相比直接运行mysqld启动,有如下好处:
- 命令更简单。我们可以直接运行mysqld_safe,它会帮我们拼接basedir/datadir/plugin-dir/user/log-error/pid-file等参数去启动mysqld。
- 运行更可靠。它会监视mysqld,有重启机制,mysqld进程异常退出时也会清理pid/socket/shutdown文件。
温馨提示:前方代码较多,若耐心不足可直接跳到最后的总结和关键词!
分析
忽略信号
trap '' 1 2 3 15
trap '' 13
忽略信号SIGHUP/SIGINT/SIGQUIT/SIGTERM/SIGPIPE。此时按Ctrl+C或者kill $PID,不会被退出。但是可以用kill -9 $PID杀掉进程。
设置参数
defaults=
case "$1" in
--no-defaults|--defaults-file=*|--defaults-extra-file=*)
defaults="$1"; shift
;;
esac
如果第一个参数为–no-defaults或–defaults-file或–defaults-extra-file,赋给defaults并将参数左移。
find_basedir_from_cmdline "$@"
# --basedir is already overridden on command line
if test -n "$MY_BASEDIR_VERSION" -a -d "$MY_BASEDIR_VERSION" ; then
# Search for mysqld and set ledir
for dir in bin libexec sbin bin ; do
if test -x "$MY_BASEDIR_VERSION/$dir/mysqld" ; then
ledir="$MY_BASEDIR_VERSION/$dir"
break
fi
done
else
# Basedir should be parent dir of bindir, unless some non-standard
# layout is used
cd "`dirname $0`"
if [ -h "$0" ] ; then
realpath="`ls -l "$0" | awk '{print $NF}'`"
cd "`dirname "$realpath"`"
fi
cd ..
MY_PWD="`pwd`"
# Search for mysqld and set ledir and BASEDIR
for dir in bin libexec sbin bin ; do
if test -x "$MY_PWD/$dir/mysqld" ; then
MY_BASEDIR_VERSION="$MY_PWD"
ledir="$MY_BASEDIR_VERSION/$dir"
break
fi
done
# If we still didn't find anything, use the compiled-in defaults
if test -z "$MY_BASEDIR_VERSION" ; then
MY_BASEDIR_VERSION='/usr/local/mysql'
ledir='/usr/local/mysql/bin'
fi
fi
find_basedir_from_cmdline () {
for arg in "$@"; do
case $arg in
--basedir=*)
MY_BASEDIR_VERSION="`echo "$arg" | sed -e 's;^--[^=]*=;;'`"
# Convert to full path
cd "$MY_BASEDIR_VERSION"
if [ $? -ne 0 ] ; then
log_error "--basedir set to '$MY_BASEDIR_VERSION', however could not access directory"
exit 1
fi
MY_BASEDIR_VERSION="`pwd`"
;;
esac
done
}
如果设置了–basedir,把对应的全路径赋给MY_BASEDIR_VERSION。sed -e 's;–[=]*=;;'表示将–到=之间的内容去掉,即只留下–basedir=后面的值。ledir设为mysqld所在目录。如果未设置–basedir,将当前脚本所在目录的父级赋给MY_BASEDIR_VERSION。如果这里面没有mysqld,使用默认值/usr/local/mysql,ledir默认值则为/usr/local/mysql/bin。
if test -d $MY_BASEDIR_VERSION/data/mysql
then
DATADIR=$MY_BASEDIR_VERSION/data
# Or just give up and use our compiled-in default
else
DATADIR=/usr/local/mysql/data
fi
if test -z "$MYSQL_HOME"
then
MYSQL_HOME=$MY_BASEDIR_VERSION
fi
export MYSQL_HOME
设置DATADIR,默认为/usr/local/mysql/data。并输出MYSQL_HOME为环境变量。
if [ -n "${PLUGIN_DIR}" ]; then
plugin_dir="${PLUGIN_DIR}"
else
# Try to find plugin dir relative to basedir
for dir in lib64/mysql/plugin lib64/plugin lib/mysql/plugin lib/plugin
do
if [ -d "${MY_BASEDIR_VERSION}/${dir}" ]; then
plugin_dir="${MY_BASEDIR_VERSION}/${dir}"
break
fi
done
# Give up and use compiled-in default
if [ -z "${plugin_dir}" ]; then
plugin_dir='/usr/local/mysql/lib/plugin'
fi
fi
plugin_dir="${plugin_dir}${PLUGIN_VARIANT}"
寻找plugin目录,默认用/usr/local/mysql/lib/plugin。
USER_OPTION=""
if test -w / -o "$USER" = "root"
then
if test "$user" != "root" -o $SET_USER = 1
then
USER_OPTION="--user=$user"
fi
if test -n "$open_files"
then
ulimit -n $open_files
fi
fi
if test -n "$open_files"
then
append_arg_to_args "--open-files-limit=$open_files"
fi
如果执行mysqld_safe不设置参数,默认user是mysql,所以这里USER_OPTION="–user=mysql"。open_files需要设置–open-files-limit才有,默认为空。
if test -z "$pid_file"
then
pid_file="$DATADIR/`hostname`.pid"
pid_file_append="`hostname`.pid"
else
pid_file_append="$pid_file"
case "$pid_file" in
/* ) ;;
* ) pid_file="$DATADIR/$pid_file" ;;
esac
fi
append_arg_to_args "--pid-file=$pid_file_append"
if test -n "$mysql_unix_port"
then
append_arg_to_args "--socket=$mysql_unix_port"
fi
if test -n "$mysql_tcp_port"
then
append_arg_to_args "--port=$mysql_tcp_port"
fi
追加参数–pid-file/–socket/–port。如果启动时未指定mysql_unix_port和mysql_tcp_port,则没有追加–socket和–port。
生成命令
cmd="`mysqld_ld_preload_text`$NOHUP_NICENESS"
for i in "$ledir/$MYSQLD" "$defaults" "--basedir=$MY_BASEDIR_VERSION" \
"--datadir=$DATADIR" "--plugin-dir=$plugin_dir" "$USER_OPTION"
do
cmd="$cmd "`shell_quote_string "$i"`
done
cmd="$cmd $args"
# Avoid 'nohup: ignoring input' warning
test -n "$NOHUP_NICENESS" && cmd="$cmd < /dev/null"
拼接启动命令和参数,并测试,其中–log-error和–pid-file来自$args。
nohup /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=DESKTOP-7GPR56L.err --pid-file=DESKTOP-7GPR56L.pid
执行命令
接下来是主循环while true
while true
do
start_time=`date +%M%S`
eval_log_error "$cmd"
...
done
eval_log_error () {
cmd="$1"
case $logging in
file)
if [ -w / -o "$USER" = "root" ]; then
cmd="$cmd > /dev/null 2>&1"
else
cmd="$cmd >> "`shell_quote_string "$err_log"`" 2>&1"
fi
;;
syslog)
cmd="$cmd --log-syslog=1 --log-syslog-facility=$syslog_facility '--log-syslog-tag=$syslog_tag' > /dev/null 2>&1"
;;
both)
if [ -w / -o "$USER" = "root" ]; then
cmd="$cmd --log-syslog=1 --log-syslog-facility=$syslog_facility '--log-syslog-tag=$syslog_tag' > /dev/null 2>&1"
else
cmd="$cmd --log-syslog=1 --log-syslog-facility=$syslog_facility '--log-syslog-tag=$syslog_tag' >> "`shell_quote_string "$err_log"`" 2>&1"
fi
;;
*)
echo "Internal program error (non-fatal):" \
" unknown logging method '$logging'" >&2
;;
esac
#echo "Running mysqld: [$cmd]"
eval "$cmd"
}
eval_log_error函数中继续拼接参数,并执行,本次使日志重定向cmd="$cmd > /dev/null 2>&1",最终形成的命令如下:
nohup /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=DESKTOP-7GPR56L.err --pid-file=DESKTOP-7GPR56L.pid < /dev/null > /dev/null 2>&1
异常处理
if test ! -f "$pid_file" # This is removed if normal shutdown
then
break
else # self's mysqld crashed or other's mysqld running
PID=`cat "$pid_file"`
if kill -0 $PID > /dev/null 2> /dev/null
then # true when above pid belongs to a running mysqld process
log_error "A mysqld process with pid=$PID is already running. Aborting!!"
exit 1
fi
fi
if test -f "$pid_file.shutdown" # created to signal that it must stop
then
log_notice "$pid_file.shutdown present. The server will not restart."
break
fi
pid文件默认位于data目录下,内容就是PID。正常关闭时会删除pid文件。如果判断已有运行中的mysqld进程,则脚本退出。如果有shutdown文件,退出循环。
if test $end_time -gt 0 -a $have_sleep -gt 0
then
# throttle down the fast restarts
if test $end_time -eq $start_time
then
fast_restart=`expr $fast_restart + 1`
if test $fast_restart -ge $max_fast_restarts
then
log_notice "The server is respawning too fast. Sleeping for 1 second."
sleep 1
sleep_state=$?
if test $sleep_state -gt 0
then
log_notice "The server is respawning too fast and no working sleep command. Turning off trottling."
have_sleep=0
fi
fast_restart=0
fi
else
fast_restart=0
fi
fi
如果频繁发生卡顿,尝试sleep1秒等待下一次循环重启,并记录日志。
if true && test $KILL_MYSQLD -eq 1
then
# Test if one process was hanging.
# This is only a fix for Linux (running as base 3 mysqld processes)
# but should work for the rest of the servers.
# The only thing is ps x => redhat 5 gives warnings when using ps -x.
# kill -9 is used or the process won't react on the kill.
numofproces=`ps xaww | grep -v "grep" | grep "$ledir/$MYSQLD\>" | grep -c "pid-file=$pid_file"`
log_notice "Number of processes running now: $numofproces"
I=1
while test "$I" -le "$numofproces"
do
PROC=`ps xaww | grep "$ledir/$MYSQLD\>" | grep -v "grep" | grep "pid-file=$pid_file" | sed -n '$p'`
for T in $PROC
do
break
done
# echo "TEST $I - $T **"
if kill -9 $T
then
log_error "$MYSQLD process hanging, pid $T - killed"
else
break
fi
I=`expr $I + 1`
done
fi
if [ ! -h "$pid_file" ]; then
rm -f "$pid_file"
fi
if [ ! -h "$safe_mysql_unix_port" ]; then
rm -f "$safe_mysql_unix_port"
fi
if [ ! -h "$pid_file.shutdown" ]; then
rm -f "$pid_file.shutdown"
fi
若KILL_MYSQLD=1,将所有mysqld进程kill掉,并清理pid/socket/shutdown三个文件。
总结
优秀的项目总有值得我们学习的地方,比如学习mysqld_safe的写法,我们可以学习它trap、nohup以及日志重定向等用法,来简单实现一个守护进程启动后台进程的脚本。此外,这时也不需要为用户终端退出导致进程结束而烦恼。当然,方法有很多,service和screen都能达到这个效果。
关键词
忽略信号;设置参数;生成命令;执行命令;异常处理