网站出现502或者数据库下线的脚本

一键布局 PHP-FPM + MySQL 双重自愈与邮件告警 系统:

第一步:创建轻量 PHP 探活文件

在新服务器终端执行:

cd /var/www/html
echo "<?php http_response_code(200); echo 'ok'; ?>" > test.php
chmod 644 healthz.php

验证测试:

curl -I https://log.vitalitys.site/test.php

确认第一行返回 HTTP/2 200 或 HTTP/1.1 200 OK。

第二步:部署邮件发信模块

如果新服务器尚未安装 Python 3,先执行安装:

yum install -y python3 || dnf install -y python3 || apt update && apt install -y python3

创建发信组件 /usr/local/bin/send_alert_mail.py:

cat << 'EOF' > /usr/local/bin/send_alert_mail.py
#!/usr/bin/env python3
import sys
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr

if len(sys.argv) < 3:
    print("Usage: python3 send_alert_mail.py <to_email> <subject> <content>")
    sys.exit(1)

to_email = sys.argv[1]
subject = sys.argv[2]
content = sys.argv[3] if len(sys.argv) > 3 else sys.stdin.read()

smtp_host = "smtp.163.com"
smtp_port = 465
sender = "[email protected]"
password = "你的授权码"  # 163 邮箱 SMTP 授权码

message = MIMEText(content, 'plain', 'utf-8')
message['From'] = formataddr(("Server Monitor", sender))
message['To'] = formataddr(("Admin", to_email))
message['Subject'] = Header(subject, 'utf-8')

try:
    server = smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=15)
    server.login(sender, password)
    server.sendmail(sender, [to_email], message.as_string())
    server.quit()
    print("SUCCESS: 邮件发送成功")
except Exception as e:
    print(f"ERROR: 邮件发送失败: {e}", file=sys.stderr)
    sys.exit(1)
EOF

chmod +x /usr/local/bin/send_alert_mail.py

快速验证:

/usr/local/bin/send_alert_mail.py "[email protected]" "【新服务器测试】发信验证 vitalityboutique.co.uk" "邮件发送模块配置正常。"

终端输出 SUCCESS: 邮件发送成功 且 QQ 邮箱收到邮件即表示通过。

第三步:部署双重监控自愈主脚本

将目标网址绑定为 [https://log.vitalitys.site/test.php](https://log.vitalitys.site/test.php 并写入主脚本:

cat << 'EOF' > /usr/local/bin/monitor_and_heal.sh
#!/usr/bin/env bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# ==============================================================================
# 网站高可用守护脚本:PHP-FPM + MySQL 双重自愈与邮件告警
# ==============================================================================

# -------------- 配置项 --------------
TARGET_URL="https://log.vitalitys.site/test.php"
PHP_SERVICE="php-fpm"

# 自动匹配数据库服务名(mysqld 或 mariadb)
if systemctl list-unit-files | grep -q "^mariadb.service"; then
    DB_SERVICE="mariadb"
else
    DB_SERVICE="mysqld"
fi

MAX_RETRIES=3
RETRY_INTERVAL=3
COOLDOWN_PERIOD=300

LOCK_FILE="/var/run/site_monitor.lock"
LOG_FILE="/var/log/site_healer.log"
PHP_RESTART_FILE="/tmp/site_last_php_restart"
DB_RESTART_FILE="/tmp/site_last_db_restart"

ALERT_EMAIL="[email protected]"
MAIL_SENDER="/usr/local/bin/send_alert_mail.py"

# -------------- 基础函数 --------------
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}

send_email_alert() {
    local subject="$1"
    local content="$2"
    local svc_name="$3"
    
    if [ -x "$MAIL_SENDER" ] && [ -n "$ALERT_EMAIL" ]; then
        local body
        body=$(cat <<EOM
监控自愈告警通知:

- 监控目标: $TARGET_URL
- 涉及服务: $svc_name
- 服务器主机名: $(hostname)
- 服务器IP: $(hostname -I | awk '{print $1}')
- 告警详情: $content
- 记录时间: $(date '+%Y-%m-%d %H:%M:%S')

详细日志请登录服务器查看: $LOG_FILE
EOM
)
        "$MAIL_SENDER" "$ALERT_EMAIL" "$subject" "$body" > /dev/null 2>&1
    fi
}

check_http_status() {
    local code
    code=$(curl -o /dev/null -s -w "%{http_code}" --connect-timeout 5 -m 8 "$TARGET_URL")
    # 数字安全校验,防止非数字解析异常
    if [[ ! "$code" =~ ^[0-9]+$ ]]; then
        echo "000"
    else
        echo "$code"
    fi
}

check_mysql_status() {
    if command -v mysqladmin >/dev/null 2>&1; then
        mysqladmin ping --silent >/dev/null 2>&1
        return $?
    else
        systemctl is-active --quiet "$DB_SERVICE"
        return $?
    fi
}

# -------------- 并发保护(防重入) --------------
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
    exit 0
fi

CURRENT_TIME=$(date +%s)

# ==============================================================================
# 模块一:MySQL 存活检测与自愈
# ==============================================================================
if ! check_mysql_status; then
    sleep 2
    if ! check_mysql_status; then
        can_restart_db=1
        if [ -f "$DB_RESTART_FILE" ]; then
            last_db_restart=$(cat "$DB_RESTART_FILE" 2>/dev/null || echo 0)
            time_diff=$((CURRENT_TIME - last_db_restart))
            if [ "$time_diff" -lt "$COOLDOWN_PERIOD" ]; then
                log "WARN: 检测到 MySQL 异常,但处于冷却期内(上次重启于 ${time_diff}s 前),跳过重启。"
                can_restart_db=0
            fi
        fi

        if [ "$can_restart_db" -eq 1 ]; then
            log "ALERT: 检测到 MySQL ($DB_SERVICE) 停止响应,开始执行重启..."
            if systemctl restart "$DB_SERVICE"; then
                echo "$CURRENT_TIME" > "$DB_RESTART_FILE"
                log "SUCCESS: MySQL ($DB_SERVICE) 重启成功。"
                send_email_alert "【数据库自愈成功】MySQL 异常已自动重启恢复" \
                                 "检测到本地 MySQL 服务停止或无法响应 ping,已成功自动执行 systemctl restart $DB_SERVICE 重启拉起。" \
                                 "$DB_SERVICE"
            else
                log "ERROR: MySQL ($DB_SERVICE) 重启失败!"
                send_email_alert "【紧急故障】MySQL ($DB_SERVICE) 重启失败!" \
                                 "检测到 MySQL 宕机,尝试执行自动重启失败,请立即登录服务器排查数据库日志!" \
                                 "$DB_SERVICE"
            fi
        fi
    fi
fi

# ==============================================================================
# 模块二:HTTP / PHP-FPM 检测与自愈
# ==============================================================================
failure_count=0
last_code=""

for i in 1 2 3; do
    code=$(check_http_status)
    last_code="$code"
    
    if [ "$code" != "200" ]; then
        failure_count=$((failure_count + 1))
    else
        break
    fi
    
    if [ "$i" -lt "$MAX_RETRIES" ]; then
        sleep "$RETRY_INTERVAL"
    fi
done

if [ "$failure_count" -ge "$MAX_RETRIES" ]; then
    can_restart_php=1
    if [ -f "$PHP_RESTART_FILE" ]; then
        last_php_restart=$(cat "$PHP_RESTART_FILE" 2>/dev/null || echo 0)
        time_diff=$((CURRENT_TIME - last_php_restart))
        if [ "$time_diff" -lt "$COOLDOWN_PERIOD" ]; then
            log "WARN: 状态码 $last_code 异常,但处于 PHP 冷却期内(上次重启于 ${time_diff}s 前),跳过重启。"
            can_restart_php=0
        fi
    fi

    if [ "$can_restart_php" -eq 1 ]; then
        log "ALERT: 目标 $TARGET_URL 连续 $MAX_RETRIES 次返回 $last_code,开始重启服务 $PHP_SERVICE..."
        if systemctl restart "$PHP_SERVICE"; then
            echo "$CURRENT_TIME" > "$PHP_RESTART_FILE"
            log "SUCCESS: 服务 $PHP_SERVICE 重启成功。"
            send_email_alert "【网站自愈成功】状态码 $last_code 已自动重启 $PHP_SERVICE" \
                             "目标探活接口连续检测到 $last_code 错误,已自动重启 $PHP_SERVICE 服务并恢复。" \
                             "$PHP_SERVICE"
        else
            log "ERROR: 服务 $PHP_SERVICE 重启失败!"
            send_email_alert "【紧急故障】服务 $PHP_SERVICE 重启失败!" \
                             "检测到异常状态码 $last_code,尝试重启 $PHP_SERVICE 失败,请立即人工介入!" \
                             "$PHP_SERVICE"
        fi
    fi
fi
EOF

chmod +x /usr/local/bin/monitor_and_heal.sh

验证测试:

/usr/local/bin/monitor_and_heal.sh

终端在 1 秒内静默无报错退出即可。

第四步:配置 Crontab 周期守护

  1. 打开定时任务:
crontab -e

2, 移到最末行,添加以下内容(5 个星号):

* * * * * /usr/local/bin/monitor_and_heal.sh >/dev/null 2>&1

3,保存后查看确认:

crontab -l

4,确保 Cron 守护进程处于运行状态:

systemctl status crond || systemctl status cron

完成后,log.vitalitys.site 站点的 PHP-FPM 和 MySQL 就处于全天候自动监控自愈守护下了。

发表回复