简介
生产环境中,经常会遇见 CPU 跑满的情况,一般是由慢 SQL 或业务逻辑导致的。为了避免程序无法访问,使用脚本监控 CPU,当达到阈值时,使用 pt-kill 杀死匹配的 sql,确保线上环境的正常运行。追加到脚本中的 SQL,可以给开发人员,作为处理故障的切入点。
使用时请根据自身情况修改脚本,本脚本仅供参考。
功能
- CPU 达到 60% 时,通知到微信
- CPU 达到 80% 时,通知到微信,kill 掉大于 20 秒的查询。
- CPU 达到 90% 时,通知到微信,kill 掉大于 60 秒的更新和大于 20 秒的查询。
- 微信通知间隔(默认 30 分钟)
流程图
以下为大致流程
CPU 监控脚本
vim /root/cpu_mon.sh #!/bin/bash # CPU 使用率大于 60 通知到微信,大于 80 通知并 kill 大于 20s 的查询,大于 90 kill 大于 20s 的查询及大于 60s 的更新 mysql_host=10.10.8.11 mysql_port=3306 mysql_user=test mysql_pwd="test123" # 企业微信脚本路径 script_dir="/root/wechat.py" # pt工具日志路径 pt_tool_log='/tmp/pt_kill.log' # 企业微信通知间隔(秒) send_interval=1800 ############################################################################################################# cpu_usage=$(top -b -n 1 | grep "Cpu(s)" | awk -F':' '{print $2}' | awk '{print $1+$3+$5+$9+$11+$13+$15}' | bc) if [ ! -e .YM5rfHdwc2R3PGRMsnVg8.txt ]; then touch ".YM5rfHdwc2R3PGRMsnVg8.txt" fi check_conn(){ # 检查 MySQL 连接 mysql -u${mysql_user} -p${mysql_pwd} -h${mysql_host} -P${mysql_port} -e "select 1" >/dev/null 2>&1 if [[ $? -ne 0 ]];then echo "$(date +"%Y-%m-%d %H:%M:%S") 无法连接 MySQL" echo "$(date +"%Y-%m-%d %H:%M:%S") 无法连接 MySQL" >> ${pt_tool_log} fi # 检查 pt 工具是否可用 pt-kill --help >/dev/null 2>&1 if [[ $? -ne 0 ]];then echo "$(date +"%Y-%m-%d %H:%M:%S") pt-kill 工具不存在" echo "$(date +"%Y-%m-%d %H:%M:%S") pt-kill 工具不存在" >> ${pt_tool_log} fi } check_conn # 获取CPU使用率 cpu_mon(){ if [ $(echo "${cpu_usage} >= 60 && ${cpu_usage} < 80"|bc) -eq 1 ];then echo 60 elif [ $(echo "${cpu_usage} >= 80 && ${cpu_usage} < 90"|bc) -eq 1 ];then echo 80 elif [ $(echo "${cpu_usage} >= 90"|bc) -eq 1 ];then echo 90 else echo 0 fi } text_80="实例信息:${mysql_host}:${mysql_port} 操作:kill 20s 以上查询 详细信息请登录数据库查看" text_90="实例信息:${mysql_host}:${mysql_port} 操作:kill 60s 以上sql 详细信息请登录数据库查看" update_sql(){ /usr/bin/pt-kill \ --victim all \ --busy-time 20 \ --match-info='^SELECT|^select' \ u=${mysql_user},p=${mysql_pwd},h=${mysql_host},P=${mysql_port} \ --kill \ --run-time=10s \ --print >> ${pt_tool_log} if [[ $? -ne 0 ]];then echo "pt-kill 执行失败,请检查具体原因" >>${pt_tool_log} echo "pt-kill 执行失败,请检查具体原因" return 1 fi /usr/bin/pt-kill \ --victim all \ --busy-time 60 \ --match-info='^update|^UPDATE' \ u=${mysql_user},p=${mysql_pwd},h=${mysql_host},P=${mysql_port} \ --kill \ --run-time=10s \ --print >> ${pt_tool_log} if [[ $? -ne 0 ]];then echo "pt-kill 执行失败,请检查具体原因" >>${pt_tool_log} echo "pt-kill 执行失败,请检查具体原因" return 1 fi } select_sql(){ /usr/bin/pt-kill \ --victim all \ --busy-time 20 \ --match-info='^SELECT|^select' \ u=${mysql_user},p=${mysql_pwd},h=${mysql_host},P=${mysql_port} \ --kill \ --run-time=10s \ --print >> ${pt_tool_log} if [[ $? -ne 0 ]];then echo "pt-kill 执行失败,请检查具体原因" >>${pt_tool_log} echo "pt-kill 执行失败,请检查具体原因" return 1 fi } # 返回通知结果 reporting_conditions(){ if [ $1 -eq 60 ];then ${script_dir} "" "通知:${mysql_host}:${mysql_port} CPU超过$(cpu_mon)%" "" if [[ $? -ne 0 ]];then echo "python 脚本执行失败" >>${pt_tool_log} fi echo "$(date +%s) 60" > .YM5rfHdwc2R3PGRMsnVg8.txt return 0 elif [ $1 -eq 80 ];then ${script_dir} "" "故障:${mysql_host}:${mysql_port} CPU超过$(cpu_mon)%" "${text_80}" if [[ $? -ne 0 ]];then echo "python 脚本执行失败" >>${pt_tool_log} fi echo "$(date +%s) 80" > .YM5rfHdwc2R3PGRMsnVg8.txt # 执行kill select_sql return 0 elif [ $1 -eq 90 ];then ${script_dir} "" "故障:${mysql_host}:${mysql_port} CPU超过$(cpu_mon)%" "${text_90}" echo "$(date +%s) 90" > .YM5rfHdwc2R3PGRMsnVg8.txt # 执行kill update_sql return 0 fi } # 发送通知 notifications(){ old_time=$(awk '{print $1}' .YM5rfHdwc2R3PGRMsnVg8.txt) old_info=$(awk '{print $2}' .YM5rfHdwc2R3PGRMsnVg8.txt) wait_time=$(echo "$(date +%s) - $old_time"|bc) # 如果文件不为空 if [ -s .YM5rfHdwc2R3PGRMsnVg8.txt ];then # 如果文件信息相同 if [ "$old_info" -eq "$1" ];then # 如果不满足等待时间 if [ $(echo "${wait_time} >= ${send_interval}"|bc) -eq 0 ];then # 跳过通知 return 0 # 满足时间等待,执行通知 else # 执行通知 reporting_conditions "$(cpu_mon)" fi # 文件信息不相同,执行通知 else # 执行通知 reporting_conditions "$(cpu_mon)" fi # 如果文件为空,执行通知 else # 执行通知 reporting_conditions "$(cpu_mon)" fi } # 设置探测次数 num_attempts=3 # 设置标志变量,表示是否执行通知 execute_notification=false # 循环进行探测 for ((i=1; i<=$num_attempts; i++)); do if [ $(cpu_mon) -ne 0 ]; then execute_notification=true else execute_notification=false break # 如果有一次等于0,立即退出循环 fi sleep 1 # 等待1秒再进行下一次探测 done # 如果连续三次都不等于0,则执行通知 if [ "$execute_notification" = true ]; then notifications "$(cpu_mon)" fi
企业微信脚本
cat /root/wechat.py #!/usr/bin/python #_*_coding:utf-8 _*_ import urllib,urllib2 import json import sys import simplejson reload(sys) sys.setdefaultencoding('utf-8') def gettoken(corpid,corpsecret): gettoken_url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=' + corpid + '&corpsecret=' + corpsecret # print gettoken_url try: token_file = urllib2.urlopen(gettoken_url) except urllib2.HTTPError as e: print e.code print e.read().decode("utf8") sys.exit() token_data = token_file.read().decode('utf-8') token_json = json.loads(token_data) token_json.keys() token = token_json['access_token'] return token def senddata(access_token,user,subject,content): send_url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' + access_token send_values = { "touser":'test', #企业号中的用户帐号,如果配置不正常,将按部门发送。 "toparty":"1", #企业号中的部门id。 "msgtype":"text", #消息类型。 "agentid":"10000123", #企业号中的应用id。 "text":{ "content":subject + '\n' + content }, "safe":"0" } # send_data = json.dumps(send_values, ensure_ascii=False) send_data = simplejson.dumps(send_values, ensure_ascii=False).encode('utf-8') print(send_data) send_request = urllib2.Request(send_url, send_data) response = json.loads(urllib2.urlopen(send_request).read()) print str(response) if __name__ == '__main__': user = str(sys.argv[1]) # 第一个参数 subject = str(sys.argv[2]) # 第二个参数 content = str(sys.argv[3]) # 第三个参数 corpid = 'ww2edb88243fd434522' #CorpID是企业号的标识 corpsecret = '_dsdf_8OPzNTxJWOlzcdfdfggtAEdsds3434dZs' #corpsecretSecret是管理组凭证密钥 accesstoken = gettoken(corpid,corpsecret) senddata(accesstoken,user,subject,content)
python 脚本基于 python2 的(linux 自带)
需要安装
simplejson
模块yum install -y python-pip # 安装 pip
pip install simplejson # 安装 simplejson 模块
⚠️使用微信报警需要在企业微信配置报警机器人,详细配置请自行百度,网上很多,可以搜索 “zabbix 配置微信报警”
配置时可能遇见 企业微信自定义应用 企业可信IP配置 的问题,点击链接解决即可。
添加定时任务
crontab -e # MySQL cpu 监控,超过阈值 kill 相关进程 */2 * * * * /bin/sh /root/cpu_mon.sh
使用时请根据自身情况修改脚本,本脚本仅供参考。