阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

MySQL启动时报“The server quit without updating PID file”错误原因深入分析

112次阅读
没有评论

共计 20850 个字符,预计需要花费 53 分钟才能阅读完成。

很多童鞋在启动 MySQL 的时候,碰到“The server quit without updating PID file”过这个错误,首先,澄清一点,出现这个错误的前提是:通过服务脚本来启动 mysql。通过 mysqld_safe 或 mysqld 启动 mysql 实例并不会报这个错误。

那么,出现这个错误的原因具体是什么呢?

哈哈,对分析过程不 care 的童鞋可直接跳到文末的总结部分~

总结

下面,来分析下 mysql 的服务启动脚本

脚本完整内容如下:

#!/bin/sh
# Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB
# This file is public domain and comes with NO WARRANTY of any kind

# MySQL daemon start/stop script.

# Usually this is put in /etc/init.d (at least on machines SYSV R4 based
# systems) and linked to /etc/rc3.d/S99mysql and /etc/rc0.d/K01mysql.
# When this is done the mysql server will be started when the machine is
# started and shut down when the systems goes down.

# Comments to support chkconfig on RedHat Linux
# chkconfig: 2345 64 36
# description: A very fast and reliable SQL database engine.

# Comments to support LSB init script conventions
### BEGIN INIT INFO
# Provides: mysql
# Required-Start: $local_fs $network $remote_fs
# Should-Start: ypbind nscd ldap ntpd xntpd
# Required-Stop: $local_fs $network $remote_fs
# Default-Start:  2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop MySQL
# Description: MySQL is a very fast and reliable SQL database engine.
### END INIT INFO
 
# If you install MySQL on some other places than /usr/local/mysql, then you
# have to do one of the following things for this script to work:
#
# - Run this script from within the MySQL installation directory
# - Create a /etc/my.cnf file with the following information:
#   [mysqld]
#   basedir=<path-to-mysql-installation-directory>
# - Add the above to any other configuration file (for example ~/.my.ini)
#   and copy my_print_defaults to /usr/bin
# - Add the path to the mysql-installation-directory to the basedir variable
#   below.
#
# If you want to affect other MySQL variables, you should make your changes
# in the /etc/my.cnf, ~/.my.cnf or other MySQL configuration files.

# If you change base dir, you must also change datadir. These may get
# overwritten by settings in the MySQL configuration files.

basedir=
datadir=

# Default value, in seconds, afterwhich the script should timeout waiting
# for server start. 
# Value here is overriden by value in my.cnf. 
# 0 means don't wait at all
# Negative numbers mean to wait indefinitely
service_startup_timeout=900

# Lock directory for RedHat / SUSE.
lockdir='/var/lock/subsys'
lock_file_path="$lockdir/mysql"

# The following variables are only set for letting mysql.server find things.

# Set some defaults
mysqld_pid_file_path=
if test -z "$basedir"
then
  basedir=/usr/local/mysql
  bindir=/usr/local/mysql/bin
  if test -z "$datadir"
  then
    datadir=/usr/local/mysql/data
  fi
  sbindir=/usr/local/mysql/bin
  libexecdir=/usr/local/mysql/bin
else
  bindir="$basedir/bin"
  if test -z "$datadir"
  then
    datadir="$basedir/data"
  fi
  sbindir="$basedir/sbin"
  libexecdir="$basedir/libexec"
fi

# datadir_set is used to determine if datadir was set (and so should be
# *not* set inside of the --basedir= handler.)
datadir_set=

#
# Use LSB init script functions for printing messages, if possible
#
lsb_functions="/lib/lsb/init-functions"
if test -f $lsb_functions ; then
  . $lsb_functions
else
  log_success_msg()
  {echo " SUCCESS! $@"
  }
  log_failure_msg()
  {echo " ERROR! $@"
  }
fi

PATH="/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin"
export PATH

mode=$1    # start or stop

[$# -ge 1 ] && shift


other_args="$*"   # uncommon, but needed when called from an RPM upgrade action
           # Expected: "--skip-networking --skip-grant-tables"
           # They are not checked here, intentionally, as it is the resposibility
           # of the "spec" file author to give correct arguments only.

case `echo "testing\c"`,`echo -n testing` in
    *c*,-n*) echo_n=   echo_c=     ;;
    *c*,*)   echo_n=-n echo_c=     ;;
    *)       echo_n=   echo_c='\c' ;;
esac

parse_server_arguments() {for arg do
    case "$arg" in
      --basedir=*)  basedir=`echo "$arg" | sed -e 's/^[^=]*=//'`
                    bindir="$basedir/bin"
            if test -z "$datadir_set"; then
              datadir="$basedir/data"
            fi
            sbindir="$basedir/sbin"
            libexecdir="$basedir/libexec"
        ;;
      --datadir=*)  datadir=`echo "$arg" | sed -e 's/^[^=]*=//'`
            datadir_set=1
    ;;
      --pid-file=*) mysqld_pid_file_path=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
      --service-startup-timeout=*) service_startup_timeout=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
    esac
  done
}

wait_for_pid () {verb="$1"           # created | removed
  pid="$2"            # process ID of the program operating on the pid-file
  pid_file_path="$3" # path to the PID file.

  i=0
  avoid_race_condition="by checking again"

  while test $i -ne $service_startup_timeout ; do

    case "$verb" in
      'created')
        # wait for a PID-file to pop into existence.
        test -s "$pid_file_path" && i='' && break
        ;;
      'removed')
        # wait for this PID-file to disappear
        test ! -s "$pid_file_path" && i='' && break
        ;;
      *)
        echo "wait_for_pid () usage: wait_for_pid created|removed pid pid_file_path"
        exit 1
        ;;
    esac

    # if server isn't running, then pid-file will never be updated
    if test -n "$pid"; then
      if kill -0 "$pid" 2>/dev/null; then
        :  # the server still runs
      else
        # The server may have exited between the last pid-file check and now.  
        if test -n "$avoid_race_condition"; then
          avoid_race_condition=""
          continue  # Check again.
        fi

        # there's nothing that will affect the file.
        log_failure_msg "The server quit without updating PID file ($pid_file_path)."
        return 1  # not waiting any more.
      fi
    fi

    echo $echo_n ".$echo_c"
    i=`expr $i + 1`
    sleep 1

  done

if test -z "$i" ; then
    log_success_msg
    return 0
  else
    log_failure_msg
    return 1
  fi
}

# Get arguments from the my.cnf file,
# the only group, which is read from now on is [mysqld]
if test -x ./bin/my_print_defaults
then
  print_defaults="./bin/my_print_defaults"
elif test -x $bindir/my_print_defaults
then
  print_defaults="$bindir/my_print_defaults"
elif test -x $bindir/mysql_print_defaults
then
  print_defaults="$bindir/mysql_print_defaults"
else
  # Try to find basedir in /etc/my.cnf
  conf=/etc/my.cnf
  print_defaults=
  if test -r $conf
  then
    subpat='^[^=]*basedir[^=]*=\(.*\)$'
    dirs=`sed -e "/$subpat/!d" -e 's//\1/' $conf`
    for d in $dirs
    do
      d=`echo $d | sed -e 's/[]//g'`
      if test -x "$d/bin/my_print_defaults"
      then
        print_defaults="$d/bin/my_print_defaults"
        break
      fi
      if test -x "$d/bin/mysql_print_defaults"
      then
        print_defaults="$d/bin/mysql_print_defaults"
        break
      fi
    done
  fi

  # Hope it's in the PATH ... but I doubt it
  test -z "$print_defaults" && print_defaults="my_print_defaults"
fi

#
# Read defaults file from 'basedir'.   If there is no defaults file there
# check if it's in the old (depricated) place (datadir) and read it from there
#

extra_args=""
if test -r "$basedir/my.cnf"
then
  extra_args="-e $basedir/my.cnf"
else
  if test -r "$datadir/my.cnf"
  then
    extra_args="-e $datadir/my.cnf"
  fi
fi

parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server`

#
# Set pid file if not given
#
if test -z "$mysqld_pid_file_path"
then
  mysqld_pid_file_path=$datadir/`hostname`.pid
else
  case "$mysqld_pid_file_path" in
    /* ) ;;
    * )  mysqld_pid_file_path="$datadir/$mysqld_pid_file_path" ;;
  esac
fi

case "$mode" in
  'start')
    # Start daemon

    # Safeguard (relative paths, core dumps..)
    cd $basedir

    echo $echo_n "Starting MySQL"
    if test -x $bindir/mysqld_safe
    then
      # Give extra arguments to mysqld with the my.cnf file. This script
      # may be overwritten at next upgrade.
      $bindir/mysqld_safe --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &
      wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$?

      # Make lock for RedHat / SuSE
      if test -w "$lockdir"
      then
        touch "$lock_file_path"
      fi

      exit $return_value
    else
      log_failure_msg "Couldn't find MySQL server ($bindir/mysqld_safe)"
    fi
    ;;

  'stop')
    # Stop daemon. We use a signal here to avoid having to know the
    # root password.

    if test -s "$mysqld_pid_file_path"
    then
      mysqld_pid=`cat "$mysqld_pid_file_path"`

      if (kill -0 $mysqld_pid 2>/dev/null)
      then
        echo $echo_n "Shutting down MySQL"
        kill $mysqld_pid
        # mysqld should remove the pid file when it exits, so wait for it.
        wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$?
      else
        log_failure_msg "MySQL server process #$mysqld_pid is not running!"
        rm "$mysqld_pid_file_path"
      fi

      # Delete lock for RedHat / SuSE
      if test -f "$lock_file_path"
      then
        rm -f "$lock_file_path"
      fi
      exit $return_value
    else
      log_failure_msg "MySQL server PID file could not be found!"
    fi
    ;;

  'restart')
    # Stop the service and regardless of whether it was
    # running or not, start it again.
    if $0 stop  $other_args; then
      $0 start $other_args
    else
      log_failure_msg "Failed to stop running server, so refusing to try to start."
      exit 1
    fi
    ;;

  'reload'|'force-reload')
    if test -s "$mysqld_pid_file_path" ; then
      read mysqld_pid <  "$mysqld_pid_file_path"
      kill -HUP $mysqld_pid && log_success_msg "Reloading service MySQL"
      touch "$mysqld_pid_file_path"
    else
      log_failure_msg "MySQL PID file could not be found!"
      exit 1
    fi
    ;;
  'status')
    # First, check to see if pid file exists
    if test -s "$mysqld_pid_file_path" ; then 
      read mysqld_pid < "$mysqld_pid_file_path"
      if kill -0 $mysqld_pid 2>/dev/null ; then 
        log_success_msg "MySQL running ($mysqld_pid)"
        exit 0
      else
        log_failure_msg "MySQL is not running, but PID file exists"
        exit 1
      fi
    else
      # Try to find appropriate mysqld process
      mysqld_pid=`pidof $libexecdir/mysqld`

      # test if multiple pids exist
      pid_count=`echo $mysqld_pid | wc -w`
      if test $pid_count -gt 1 ; then
        log_failure_msg "Multiple MySQL running but PID file could not be found ($mysqld_pid)"
        exit 5
      elif test -z $mysqld_pid ; then 
        if test -f "$lock_file_path" ; then 
          log_failure_msg "MySQL is not running, but lock file ($lock_file_path) exists"
          exit 2
        fi 
        log_failure_msg "MySQL is not running"
        exit 3
      else
        log_failure_msg "MySQL is running but PID file could not be found"
        exit 4
      fi
    fi
    ;;
    *)
      # usage
      basename=`basename "$0"`
      echo "Usage: $basename  {start|stop|restart|reload|force-reload|status}  [MySQL server options]"
      exit 1
    ;;
esac

exit 0

首先,定义相关参数

basedir=
datadir=

# Default value, in seconds, afterwhich the script should timeout waiting
# for server start. 
# Value here is overriden by value in my.cnf. 
# 0 means don't wait at all
# Negative numbers mean to wait indefinitely
service_startup_timeout=900

# Lock directory for RedHat / SuSE.
lockdir='/var/lock/subsys'
lock_file_path="$lockdir/mysql"

其中,

basedir 指的二进制压缩包解压后所在的目录,譬如 /usr/local/mysql。

datadir 指的是数据目录

service_startup_timeout=900 定义 mysql 服务启动的时间限制,如果在 900s 中没有启动成功,则该脚本会退出。

lockdir=’/var/lock/subsys’

关于 /var/lock/subsys,网上的解释如下,后续会用到。

 总的来说,系统关闭的过程(发出关闭信号,调用服务自身的进程)中会检查 /var/lock/subsys 下的文件,逐一关闭每个服务,如果某一运行的服务在 /var/lock/subsys 下没有相应的选项。在系统关闭的时候,会像杀死普通进程一样杀死这个服务。通过察看 /etc/rc.d/init.d 下的脚本,可以发现每个服务自己操纵时都会去查看 /var/lock/subsys 下相应的服务。很多程序需要判断是否当前已经有一个实例在运行,这个目录就是让程序判断是否有实例运行的标志,比如说 xinetd,如果存在这个文件,表示已经有 xinetd 在运行了,否则就是没有,当然程序里面还要有相应的判断措施来真正确定是否有实例在运行。通常与该目录配套的还有 /var/run 目录,用来存放对应实例的 PID,如果你写脚本的话,会发现这 2 个目录结合起来可以很方便的判断出许多服务是否在运行,运行的相关信息等等。

判断 basedir 和 datadir

# Set some defaults
mysqld_pid_file_path=
if test -z "$basedir"
then
  basedir=/usr/local/mysql
  bindir=/usr/local/mysql/bin
  if test -z "$datadir"
  then
    datadir=/usr/local/mysql/data
  fi
  sbindir=/usr/local/mysql/bin
  libexecdir=/usr/local/mysql/bin
else
  bindir="$basedir/bin"
  if test -z "$datadir"
  then
    datadir="$basedir/data"
  fi
  sbindir="$basedir/sbin"
  libexecdir="$basedir/libexec"
fi

其中,

mysqld_pid_file_path 指定 pid 文件的路径

-z string 判断字符串是否为空

如果 basedir 没有显示设置,则默认为 /usr/local/mysql,这也是为什么很多 mysql 安装教程都推荐将 mysql 相关文件放到 /usr/local/mysql 下。

如果 datadir 没有显示设置,则默认为 $basedir/data。

定义 log_success_msg() 和 log_failure_msg() 函数

首先,判断 /lib/lsb/init-functions 文件是否存在,如果存在,则使定义在 init-functions 文件中的所有 shell 函数在当前脚本中生效。

如果没有,则定义两个函数,一个用于打印成功日志,一个是打印错误日志。

在 RHCS 6.7 中,该文件并不存在,已被 /etc/init.d/functions 所替代。

#
# Use LSB init script functions for printing messages, if possible
#
lsb_functions="/lib/lsb/init-functions"
if test -f $lsb_functions ; then
  . $lsb_functions
else
  log_success_msg()
  {echo " SUCCESS! $@"
  }
  log_failure_msg()
  {echo " ERROR! $@"
  }
fi 

传递参数

将第一个参数传递给 mode,剩下的参数传递给 other_args

PATH="/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin"
export PATH

mode=$1    # start or stop

[$# -ge 1 ] && shift


other_args="$*"   # uncommon, but needed when called from an RPM upgrade action
           # Expected: "--skip-networking --skip-grant-tables"
           # They are not checked here, intentionally, as it is the resposibility
           # of the "spec" file author to give correct arguments only.

case `echo "testing\c"`,`echo -n testing` in
    *c*,-n*) echo_n=   echo_c=     ;;
    *c*,*)   echo_n=-n echo_c=     ;;
    *)       echo_n=   echo_c='\c' ;;
esac 

解析配置文件中的参数

这个函数在脚本后面会涉及到。

主要涉及如下参数:–basedir,–datadir,–pid-file,–service-startup-timeout。

parse_server_arguments() {for arg do
    case "$arg" in
      --basedir=*)  basedir=`echo "$arg" | sed -e 's/^[^=]*=//'`
                    bindir="$basedir/bin"
            if test -z "$datadir_set"; then
              datadir="$basedir/data"
            fi
            sbindir="$basedir/sbin"
            libexecdir="$basedir/libexec"
        ;;
      --datadir=*)  datadir=`echo "$arg" | sed -e 's/^[^=]*=//'`
            datadir_set=1
    ;;
      --pid-file=*) mysqld_pid_file_path=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
      --service-startup-timeout=*) service_startup_timeout=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
    esac
  done
}

判断 my_print_defaults 的位置

首先,它判断当前路径下的 bin 目录中是否存在该可执行文件,如果不存在,则再判断 $bindir(通常指的是 $basedir/bin)目录下是否存在。

如果还是没有,则会判断 /etc/my.cnf 是否存在并且可读,如果是,则判断该配置文件中是否指定了 basedir 参数,

如果指定了,则取出该参数的值,并判断该值对应的目录中是否存在 bin/my_print_defaults 可执行文件

最后一步,如果在上述目录中实在没发现 my_print_defaults 文件,

索性就将 print_defaults 设置为 ”my_print_defaults”,寄希望于该命令在当前的 PATH 环境中。

# Get arguments from the my.cnf file,
# the only group, which is read from now on is [mysqld]
if test -x ./bin/my_print_defaults
then
  print_defaults="./bin/my_print_defaults"
elif test -x $bindir/my_print_defaults
then
  print_defaults="$bindir/my_print_defaults"
elif test -x $bindir/mysql_print_defaults
then
  print_defaults="$bindir/mysql_print_defaults"
else
  # Try to find basedir in /etc/my.cnf
  conf=/etc/my.cnf
  print_defaults=
  if test -r $conf
  then
    subpat='^[^=]*basedir[^=]*=\(.*\)$'
    dirs=`sed -e "/$subpat/!d" -e 's//\1/' $conf`
    for d in $dirs
    do
      d=`echo $d | sed -e 's/[]//g'`
      if test -x "$d/bin/my_print_defaults"
      then
        print_defaults="$d/bin/my_print_defaults"
        break
      fi
      if test -x "$d/bin/mysql_print_defaults"
      then
        print_defaults="$d/bin/mysql_print_defaults"
        break
      fi
    done
  fi

  # Hope it's in the PATH ... but I doubt it
  test -z "$print_defaults" && print_defaults="my_print_defaults"
fi

查找默认的配置文件

-r file 如果文件可读,则为真

#
# Read defaults file from 'basedir'.   If there is no defaults file there
# check if it's in the old (depricated) place (datadir) and read it from there
#

extra_args=""
if test -r "$basedir/my.cnf"
then
  extra_args="-e $basedir/my.cnf"
else
  if test -r "$datadir/my.cnf"
  then
    extra_args="-e $datadir/my.cnf"
  fi
fi

解析配置文件中的参数

my_print_defaults 的用法如下:

my_print_defaults –defaults-file=example.cnf client mysql

即读取配置文件中,client 和 mysql 部分的参数配置,

具体在本脚本中,是读取 mysqld,server,mysql_server,mysql.server 四个部分的配置参数。

parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server`

设置 pid file 的路径

-z string 判断字符串是否为空

如果 –pid-file 没有在读取到的配置文件中设置或者脚本刚开始的 mysqld_pid_file_path 参数没有设置,

则 pid file 默认设置在 datadir 下,以主机名.pid 命名。

如果该参数设置了,还需要进一步判断

如果该参数中带有斜杠,则代表给定的值带有路径,可直接使用。

如果该参数中没带路径,则代表给定的值只是 pid 的文件名,可将其设在 datadir 下。

#
# Set pid file if not given
#
if test -z "$mysqld_pid_file_path"
then
  mysqld_pid_file_path=$datadir/`hostname`.pid
else
  case "$mysqld_pid_file_path" in
    /* ) ;;
    * )  mysqld_pid_file_path="$datadir/$mysqld_pid_file_path" ;;
  esac
fi

服务脚本 start 选项

首先,切换到 $basedir 中

其次,判断 $basedir/bin 中的 mysqld_safe 是否是可执行文件,如果是,则启动 mysqld 实例,如果不是,则报错退出。

那么,启动流程又是如何实现的呢?

首先,执行 $bindir/mysqld_safe –datadir=”$datadir” –pid-file=”$mysqld_pid_file_path” $other_args >/dev/null 2>&1 & 命令,启动 mysqld 实例。

注意到没有,mysqld_safe 其实是在 basedir 中执行的,包括 mysql 初始化脚本 mysql_install_db,也建议在 basedir 中执行,具体可参考:

分析 MariaDB 初始化脚本 mysql_install_db

然后通过 wait_for_pid 函数进行判断,具体可见下文对于 wait_for_pid 函数的分析

判断完毕后,

查看 $lockdir 目录是否可写,可写的话,则在目录上创建一个文件。

case "$mode" in
  'start')
    # Start daemon

    # Safeguard (relative paths, core dumps..)
    cd $basedir

    echo $echo_n "Starting MySQL"
    if test -x $bindir/mysqld_safe
    then
      # Give extra arguments to mysqld with the my.cnf file. This script
      # may be overwritten at next upgrade.
      $bindir/mysqld_safe --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &
      wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$?

      # Make lock for RedHat / SuSE
      if test -w "$lockdir"
      then
        touch "$lock_file_path"
      fi

      exit $return_value
    else
      log_failure_msg "Couldn't find MySQL server ($bindir/mysqld_safe)"
    fi
    ;;

wait_for_pid 函数

在利用 mysqld_safe 启动 mysql 实例后,会调用该参数

wait_for_pid created “$!” “$mysqld_pid_file_path”; return_value=$?

其中 $! 在 shell 中用于获取最后运行的后台 Process 的 PID,具体在本例中,是 mysqld_safe 进程的 pid。

因为第一个参数是 created,所以会执行 test -s “$pid_file_path” && i=” && break 命令。

-s file 如果文件的长度不为零,则为真

该命令的意思是如果 pid 文件存在,则将变量 i 设置为空,并退出 while 循环。

然后执行如下判断,

if test -z "$i" ; then
    log_success_msg
    return 0
  else
    log_failure_msg
    return 1
  fi

如果 $i 为空,则打印成功日志,并退出脚本,很显然,在 pid 文件存在的情况���,会将变量 i 设置为空。

再来看看 pid 文件不存在的情况

首先,会判断 $pid 是否不为空(即 if test -n “$pid”)

如果不为空,则代表在执行完 mysqld_safe 后,已经捕捉到了该进程的 pid。

在这种情况下,进一步通过 kill -0 “$pid” 确认该进程是否存在。

kill - 0 就是不发送任何信号, 但是系统会进行错误检查, 所以经常用来检查一个进程是否存在, 当进程不存在时, kill -0 pid 会返回错误

如果该进程存在,则不执行任何操作,直接跳到如下操作

echo $echo_n ".$echo_c"
i=`expr $i + 1`
sleep 1

将变量 i 加 1,并 sleep 1s。

然后,继续 while 循环,之所以这样做,是考虑到 mysqld_safe 已经执行,但是 mysqld 实例还在启动过程中,还没创建好 pid 文件。

一直到 $1 达到 $service_startup_timeout 定义的时长。

如果在 while 循环的过程中,通过 kill -0 “$pid” 判断到进程已经不存在了,

则会再判断一次,如果这次判断的结果依旧是 pid file 不存在,且进程不存在,则会执行

log_failure_msg “The server quit without updating PID file ($pid_file_path).”

这就是大名鼎鼎的“The server quit without updating PID file”的由来。

wait_for_pid () {verb="$1"           # created | removed
  pid="$2"            # process ID of the program operating on the pid-file
  pid_file_path="$3" # path to the PID file.

  i=0
  avoid_race_condition="by checking again"

  while test $i -ne $service_startup_timeout ; do

    case "$verb" in
      'created')
        # wait for a PID-file to pop into existence.
        test -s "$pid_file_path" && i='' && break
        ;;
      'removed')
        # wait for this PID-file to disappear
        test ! -s "$pid_file_path" && i='' && break
        ;;
      *)
        echo "wait_for_pid () usage: wait_for_pid created|removed pid pid_file_path"
        exit 1
        ;;
    esac

    # if server isn't running, then pid-file will never be updated
    if test -n "$pid"; then
      if kill -0 "$pid" 2>/dev/null; then
        :  # the server still runs
      else
        # The server may have exited between the last pid-file check and now.  
        if test -n "$avoid_race_condition"; then
          avoid_race_condition=""
          continue  # Check again.
        fi

        # there's nothing that will affect the file.
        log_failure_msg "The server quit without updating PID file ($pid_file_path)."
        return 1  # not waiting any more.
      fi
    fi

    echo $echo_n ".$echo_c"
    i=`expr $i + 1`
    sleep 1

  done

if test -z "$i" ; then
    log_success_msg
    return 0
  else
    log_failure_msg
    return 1
  fi
}

服务脚本 stop 选项

首先,判断 pid 文件的长度是否不为零。

-s file 如果文件的长度不为零,则为真

此时,会通过 pid 文件获取 mysqld 进程的 pid,注意,不是 mysqld_safe 进程的 pid

然后,判断 mysqld 进程是否在正常运行,

如果是,则通过 kill $mysqld_pid 的方式来关闭 mysqld 进程

 杀死进程最安全的方法是单纯使用 kill 命令,不加修饰符,不带标志。标准的 kill 命令通常会终止有问题的进程,并把进程的资源释放给系统。然而,如果进程启动了子进程,只杀死父进程,子进程仍在运行,因此仍消耗资源。为了防止这些所谓的“僵尸进程”,应确保在杀死父进程之前,先杀死其所有的子进程。

然后,调用 wait_for_pid 函数进行判断,其实,wait_for_pid 函数中设置 avoid_race_condition 变量的目的是为了 stop 选项,确实有可能出现,mysqld 是在检查 pid file 之后,检查进程是否存活之前退出的。

如果 mysqld 进程没有正常运行,在会打印“MySQL server process #$mysqld_pid is not running!”信息,并删除 pid 文件。

如果在执行 stop 的时候,判断 pid 文件的长度为 0,则会打印 ”MySQL server PID file could not be found!” 信息。

所以,在 pid 文件不存在的情况下,通过服务脚本执行 stop 选项并不会关闭 mysqld 进程,这个时候,就可通过 kill $mysqld_pid 的方式来关闭 mysqld 进程。

'stop')
    # Stop daemon. We use a signal here to avoid having to know the
    # root password.

    if test -s "$mysqld_pid_file_path"
    then
      mysqld_pid=`cat "$mysqld_pid_file_path"`

      if (kill -0 $mysqld_pid 2>/dev/null)
      then
        echo $echo_n "Shutting down MySQL"
        kill $mysqld_pid
        # mysqld should remove the pid file when it exits, so wait for it.
        wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$?
      else
        log_failure_msg "MySQL server process #$mysqld_pid is not running!"
        rm "$mysqld_pid_file_path"
      fi

      # Delete lock for RedHat / SuSE
      if test -f "$lock_file_path"
      then
        rm -f "$lock_file_path"
      fi
      exit $return_value
    else
      log_failure_msg "MySQL server PID file could not be found!"
    fi
    ;;

服务脚本 restart 选项

首先,先执行 stop 操作,如果 stop 操作成功的话,则继续执行 start 操作。

如果 stop 操作失败的话,则会输出 ”Failed to stop running server, so refusing to try to start.” 信息,并退出脚本。

'restart')
    # Stop the service and regardless of whether it was
    # running or not, start it again.
    if $0 stop  $other_args; then
      $0 start $other_args
    else
      log_failure_msg "Failed to stop running server, so refusing to try to start."
      exit 1
    fi
    ;;

服务脚本 reload 选项

首先,判断 pid 文件的长度是否为 0,如果不为 0,则将该文件中的值设置为 mysqld_pid 变量的值。

然后对该进程执行 kill -HUP 操作。

kill -HUP pid 

pid 是进程标识。如果想要更改配置而不需停止并重新启动服务,请使用该命令。在对配置文件作必要的更改后,发出该命令以动态更新服务配置。根据约定,当您发送一个挂起信号(信号 1 或 HUP)时,大多数服务器进程(所有常用的进程)都会进行复位操作并重新加载它们的配置文件。

如果 pid 文件的长度为 0,则输出 ”MySQL PID file could not be found!”。

'reload'|'force-reload')
    if test -s "$mysqld_pid_file_path" ; then
      read mysqld_pid <  "$mysqld_pid_file_path"
      kill -HUP $mysqld_pid && log_success_msg "Reloading service MySQL"
      touch "$mysqld_pid_file_path"
    else
      log_failure_msg "MySQL PID file could not be found!"
      exit 1
    fi
    ;; 

服务脚本 status 选项

首先,判断 pid 文件长度是否为 0,如果不是,则读取该文件中的值,并判断 pid 对应的进程是否运行正常,

如果运行正常,则输出 ”MySQL running”

如果不正常,则输出 ”MySQL is not running, but PID file exists”

如果 pid 文件的长度为 0,则试图通过 mysqld 的启动命令来获取其 pid,

这个时候,可能存在一个 mysqld 程序启动了多个实例,这会导致 pid_count=`echo $mysqld_pid | wc -w` 大于 1。

这个时候,会输出 ”Multiple MySQL running but PID file could not be found” 信息,并退出脚本。

如果 mysqld_pid 为空,则会继续判断 ”$lock_file_path” 是否存在,如果存在,

则会输出 ”MySQL is not running, but lock file ($lock_file_path) exists” 信息。

如果 ”$lock_file_path” 不存在,则会输出 ”MySQL is not running” 信息。

如果 mysqld_pid 等于 1,则会输出 ”MySQL is running but PID file could not be found” 信息。

'status')
    # First, check to see if pid file exists
    if test -s "$mysqld_pid_file_path" ; then 
      read mysqld_pid < "$mysqld_pid_file_path"
      if kill -0 $mysqld_pid 2>/dev/null ; then 
        log_success_msg "MySQL running ($mysqld_pid)"
        exit 0
      else
        log_failure_msg "MySQL is not running, but PID file exists"
        exit 1
      fi
    else
      # Try to find appropriate mysqld process
      mysqld_pid=`pidof $libexecdir/mysqld`

      # test if multiple pids exist
      pid_count=`echo $mysqld_pid | wc -w`
      if test $pid_count -gt 1 ; then
        log_failure_msg "Multiple MySQL running but PID file could not be found ($mysqld_pid)"
        exit 5
      elif test -z $mysqld_pid ; then 
        if test -f "$lock_file_path" ; then 
          log_failure_msg "MySQL is not running, but lock file ($lock_file_path) exists"
          exit 2
        fi 
        log_failure_msg "MySQL is not running"
        exit 3
      else
        log_failure_msg "MySQL is running but PID file could not be found"
        exit 4
      fi
    fi
    ;;

服务脚本其它选项

如果脚本的第一个参数不是上述几个选项,则会输出 Usage 信息。

*)
      # usage
      basename=`basename "$0"`
      echo "Usage: $basename  {start|stop|restart|reload|force-reload|status}  [MySQL server options]"
      exit 1
    ;;

至此,mysql 的服务脚本分析完毕~

总结

在通过服务脚本启动 mysql 的过程中,报“The server quit without updating PID file”错误,有两个条件

首先,pid 文件不存在

其次,通过 kill -0 $pid 检查到进程并不存在

这个时候,只能通过 mysql 数据库的错误日志来定位。

服务脚本如果不做任何调整的话,默认的 basedir 是 /usr/local/mysql,datadir 是 /usr/local/mysql/data

如果自己的 mysql 服务不是默认路径,

则需要在该脚本中显式设置

经测试,需设置如下几处:

1. 设置 basedir 和添加 conf 变量

其中,conf 指的是 mysqld 的配置文件,建议配置文件中显式指定 basedir 和 datadir 的值。

在这里,datadir 可不设置,因为 datadir 可通过配置文件来获取。

但是 basedir 必须要指定,因为要首先根据 basedir 来判断 my_print_deefauts 命令

basedir=/usr/local/mysql-advanced-5.6.23-linux-glibc2.5-x86_64
datadir=
conf=/usr/local/mysql-advanced-5.6.23-linux-glibc2.5-x86_64/my_3308.cnf

2. 第 256 行,添加 extra_args=” -c $conf”

extra_args=" -e $basedir/my.cnf.bak"
if test -r "$basedir/my.cnf"
then
  extra_args="-e $basedir/my.cnf"
else
  if test -r "$datadir/my.cnf"
  then
    extra_args="-e $datadir/my.cnf"
  fi
fi
extra_args=" -c $conf" 

3. 修改 285 行 mysqld_safe 的启动参数

$bindir/mysqld_safe --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &

修改为,

$bindir/mysqld_safe --defaults-file="$conf" --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &

主要是添加了 –defaults-file 选项

本文永久更新链接地址 :http://www.linuxidc.com/Linux/2017-05/143912.htm

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-22发表,共计20850字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中