MySQL系列(2):mysqld_safe解析

引言

上一节中,我们手工编译了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都能达到这个效果。

关键词

忽略信号;设置参数;生成命令;执行命令;异常处理


欢迎关注公众号,获取推送更方便,遇到问题来交流!

技术长跑

猜你喜欢

转载自blog.csdn.net/CanvaChen/article/details/102886375