在生产环境中,常见MySQL架构使用最多的就是主从了,因此对于主从是否同步数据的监控尤为重要。如果使用了专业的监控软件(如zabbix)监控MySQL,那么选择监控工具提供的模板或插件去监控就很方便,但是如果涉及到一些特殊要求就另当别论了。我们可以写shell脚本来实现定制化的需求。
本案例需要写一个shell脚本来监控MySQL主从,需求如下:
1)每分钟检测一次,本次执行脚本时需检测上一次是否执行完成,如果还未完成则本次不执行
2)如果不同步需要发送告警邮件给
3)需要做告警收敛,在没有解决问题前每隔30分钟发一次告警邮件
4)假设本机mysql root账户密码为tpH40Kznv
知识点一:特殊符号$?
在命令行或者在shell中,每执行完一条命令,都会有一个返回值,返回值为0则表示这条命令执行成功,非0表示执行不成功,返回值可以通过$?查看,如下:
# ls
123 1.txt
# echo $?
0
在shell脚本中,要想判断一条命令是否执行成功,就可以使用$?的值是否为0来实现。
知识点二:查看MySQL主从状态
判断MySQL主从是否正常有两种方法,第一种是对比两台MySQL上的数据是否一致,第二种是通过执行“show slave status”指令查看输出的结果,判断主从状态。第一种是最精准的,但是比较麻烦,所以通常用第二种来判断MySQL主从状态。
操作如下(在从机上执行):
# mysql -uroot -ptpH40Kznv -e "show slave status \G"
*************************** 1. row ***************************
Slave_IO_State: Connecting to master
Master_Host: 192.168.93.130
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: aminglinux1.000001
Read_Master_Log_Pos: 664307
Relay_Log_File: CLAY-relay-bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: aminglinux1.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 664307
Relay_Log_Space: 120
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 2003
Last_IO_Error: error connecting to master 'repl@192.168.93.130:3306' - retry-time: 60 retries: 1
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 0
Master_UUID:
Master_Info_File: /data/mysql/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp: 190718 18:49:05
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
需要关注的行有:Slave_IO_Running、Slave_SQL_Running,只有这两行的值全部是Yes才算是主从同步状态正常。 任何一个为No,则不正常,当不正常时需要查看下面的Error(如Last_Error、Last_IO_error、Last_SQL_Error)相关信息进一步判断造成不同步的原因。
在生产环境中,有很大一部分问题不是主从不同步,而是主从延迟太严重,对于以上输出信息中,有一行显示的是主从延迟信息,
Seconds_Behind_Master,这个显示的是一个时间,单位是秒,表示从落后主多少秒。这个值其实并不完全精准,但我们却可以通过这个数值判断主从是否有延迟(保证主从两台机器时间设置一致)。
知识点三:shell中的数学运算
shell中的加减乘除和取余有点特殊,主要是格式上和C不太一样。
# a=1;b=2
# c=$[$a+$b]
# echo $c
3
# d=$[$b-$a]
# echo $d
1
# e=$[$a*$b]
# echo $e
2
# f=$[$b/$a]
# echo $f
2
# g=$[$b%$a] //取余
# echo $g
0
取余只能整数除以整数,若除数比被除数大,直接除数就是余数;若除数比被除数小,被除数就除以除数直到剩下的数比除数小,则这个数就是余数,而且注意余数的符号要与被除数的符号一致。
c÷b,读作c除以b(或b除c)。其中,c叫做被除数,b叫做除数。
在shell的数学运算中要想使用小数,或者限定小数位数,可以借助bc的scale实现:
# a=10;b=3
# echo $[$a/$b]
3
# echo "scale=2;$a/$b"|bc //scale等于2表示小数有2位
3.33
知识点四:查看MySQL队列
在Linux系统中,可以通过ps、top等指令来查看系统的进程情况,在MySQL中,可以通过“show porcesslist"指令查看MySQL进程队列。当MySQL服务器负载变高或者访问卡顿时,查看一下进程队列非常有必要。操作如下:
# mysql -uroot -ptpH40Kznv -e "show processlist"
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+-------+------------------+
| 2 | root | localhost | NULL | Query | 0 | init | show processlist |
+----+------+-----------+------+---------+------+-------+------------------+
因为是实验机器并没有什么访问量,所以只列出了show processlist本身。
有时候列出来的队列命令太长从而显示不完整,则需要使用另外一个指令:show full processlist。
知识点五:shell中的内置变量
在shell脚本中,你会不会奇怪,哪来的$1和$2,其实就是shell脚本中的预设变量,其中$1的值就是在执行的时候输入的第一个参数,$2就是执行的时候输入的第二个参数,shell脚本的预设变量是没有限制的。另外还有一个$0,它代表的是脚本本身的名字,编写一个测试脚本,如下:
# vim option.sh
#!/bin/bash
echo "$1 $2 $0"
执行结果如下:
# sh option.sh 1 2
1 2 option.sh
如果这样执行脚本,那$0的值会有所不同,如下:
# chmod a+x option.sh
# ./option.sh 1 2
1 2 ./option.sh
就是说,$0的值会跟着执行时的命令变化,用绝对路径它就会显示绝对路径,用相对路径它就会显示相对路径。
本案例参考脚本:
完成本脚本最核心的一个知识点就是查看slave的状态,然后借助grep、sed、awk把想要的字符串截取出来,然后进行判断是否和预期一致。脚本中的mail.py参考案例二中的mail.py。
#!/bin/bash
##检测MySQL主从是否同步
##作者:
##日期:
##版本:v0.1
#把脚本名字存入变量s_name
s_name=`echo $0 | awk -F '/' '{print $NF}'`
Mysql_c="mysql -uroot -ptpH40Kznv"
mail_user=aming_test@163.com
#该函数实现邮件告警收敛,在案例五中出现过,通用
m_mail(){
log=$1 #此处的$1表示第一个函数chk_sp
t_s=`date +%s`
t_s2=`date -d "1 hours ago" +%s`
if [ ! -f /tmp/$log ]
then
#创建$log文件
touch /tmp/$log
#增加a权限,只允许追加内容,不允许更改或删除
chattr +a /tmp/$log
#第一次告警,可以直接写入1小时以前的时间戳
echo $t_s2 >> /tmp/$log
fi
#无论$log文件是否是刚刚创建,都需要查看最后一行的时间戳
t_s2=`tail -1 /tmp/$log|awk '{print $1}'`
#取出最后一行即上次告警的时间戳后,立即写入当前的时间戳
echo $t_s>>/tmp/$log
#取两次时间戳差值
v=$[$t_s-$t_s2]
#如果差值超过1800,立即发邮件
if [ $v -gt 1800 ]
then
#发邮件,其中$2为mail函数的第二个参数,这里为一个文件
python mail.py $mail_user "磁盘使用率超过90%" "`cat $2`" 2>/dev/null
#定义计数器临时文件,并写入0
echo "0" > /tmp/$log.count
else
#如果计数器临时文件不存在,需要创建并写入0
if [ ! -f /tmp/$log.count ]
then
echo "0" > /tmp/$log.count
fi
nu=`cat /tmp/$log.count`
#30分钟内每发生一次告警,计数器加1
nu2=$[$nu+1]
echo $nu2>/tmp/$log.count
#当告警次数超过30次,需要再次发邮件
if [ $nu2 -gt 30 ]
then
python mail.py $mail_user "磁盘使用率超过90%持续30分钟了" "`cat $2`" 2>/dev/null
#第二次告警后,将计数器再次从0开始
echo "0" > /tmp/$log.count
fi
fi
}
#把进程情况存入临时文件,如果加管道求行数会有问题
ps aux |grep "$s_name" |grep -vE "$$|grep">/tmp/ps.tmp
p_n=`wc -l /tmp/ps.tmp|awk '{print $1}'`
#当进程数大于0,则说明上次的脚本还未执行完
if [ $p_n -gt 0 ]
then
exit
fi
#先执行一条show processlist,看是否执行成功
$Mysql_c -e "show processlist" >/tmp/mysql_pro.log 2>/tmp/mysql_log.err
#如果上一条命令执行不成功,说明这个MySQL服务出现问题了。
if [ $? -gt 0 ]
then
m_mail mysql_service_error /tmp/mysql_log.err
exit
else
$Mysql_c -e "show slave status\G" >/tmp/mysql_s.log
n1=`wc -l /tmp/mysql_s.log | awk '{print $1}'`
if [ $n1 -gt 0 ]
then
y1=`grep 'Slave_IO_Running:' /tmp/mysql_s.log | awk -F : '{print $2}' | sed 's/ //g'`
y2=`grep 'Slave_SQL_Running:' /tmp/mysql_s.log | awk -F : '{print $2}' | sed 's/ //g'`
if [ $y1 == "No" ] || [ $y2 == "No" ]
then
m_mail mysql_slavestatus_error /tmp/mysql_s.log
fi
fi
fi