PHP项目演练脚本如何模拟故障验证自愈能力

wen PHP项目 31

本文目录导读:

PHP项目演练脚本如何模拟故障验证自愈能力

  1. 核心设计思路
  2. 典型故障模拟脚本(Bash + PHP 命令)
  3. 演练的执行与断言
  4. 自愈能力验证的关键断言点
  5. 特别提示(生产环境演练)

在PHP项目中模拟故障并验证自愈能力,通常需要从基础设施层面(如进程、网络、存储)和应用层面(如数据库、缓存、第三方API)两个维度入手,以下是一套可执行的实战演练方案。


核心设计思路

  1. 可观测性先行:确保演练前已部署日志、指标(如QPS、错误率)和告警。
  2. 最小影响原则:优先在预发布环境或业务低峰期进行,或使用服务降级后的影子副本。
  3. 自动化断言:演练结束后自动检查服务是否恢复至预定义的健康基线。

典型故障模拟脚本(Bash + PHP 命令)

以下脚本假设您的PHP服务通过 php-fpmsupervisord 管理,并且具备基本的健康检查端点(如 /health)。

模拟进程级故障(PHP-FPM崩溃)

#!/bin/bash
# simulate_php_fpm_crash.sh
SERVICE_NAME="php8.2-fpm"
HEALTH_CHECK_URL="http://localhost/health"
echo "=== 开始模拟 PHP-FPM 进程崩溃 ==="
# 1. 记录初始状态
initial_status=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_CHECK_URL)
echo "初始健康状态码: $initial_status"
# 2. 模拟故障:强制kill所有PHP-FPM进程
echo "正在停止 PHP-FPM..."
sudo systemctl stop $SERVICE_NAME
# 或者更暴力:sudo pkill -9 php-fpm
# 3. 验证服务已不可用
echo "等待2秒后检查故障状态..."
sleep 2
fail_status=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_CHECK_URL || echo "000")
echo "故障后健康状态码: $fail_status"
if [ "$fail_status" == "000" ] || [ "$fail_status" -ge 500 ]; then
    echo "✅ 故障模拟成功:PHP-FPM已不可用"
else
    echo "⚠️ 警告:服务仍然响应,请检查服务管理机制"
fi
# 4. 触发自愈机制(假设有systemd的Restart=always或supervisord)
echo "正在启动服务自愈流程(停止后的自动restart可能需要数秒至数分钟)..."
# 方案A:systemd已配置自动重启,则等待它恢复
if systemctl is-enabled $SERVICE_NAME &>/dev/null; then
    echo "等待systemd自动重启(最长60秒)..."
    for i in {1..30}; do
        sleep 2
        curl_result=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_CHECK_URL)
        if [ "$curl_result" -eq 200 ] || [ "$curl_result" -eq 302 ]; then
            echo "✅ 自愈成功!服务已在第${i}次检查后恢复 (HTTP $curl_result)"
            break
        fi
        if [ $i -eq 30 ]; then
            echo "❌ 自愈超时,请手动检查服务状态"
            exit 1
        fi
    done
else
    # 方案B:手动启动(用于没有自动重启机制的环境)
    echo "未检测到自动重启机制,手动启动服务..."
    sudo systemctl start $SERVICE_NAME
    sleep 3
fi
# 5. 最终验证
final_status=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_CHECK_URL)
if [ "$final_status" -eq 200 ] || [ "$final_status" -eq 302 ]; then
    echo "最终状态: HTTP $final_status - 自愈验证通过 ✅"
    exit 0
else
    echo "❌ 自愈失败,最终状态HTTP $final_status"
    exit 1
fi

模拟数据库连接故障(MySQL连接池耗尽/断连)

#!/bin/bash
# simulate_db_failure.sh
DB_HOST="127.0.0.1"
DB_PORT=3306
APP_CONTAINER_NAME="my_php_app"   # 如果是Docker环境
HEALTH_CHECK_URL="http://localhost/api/ping-db"   # 应用内数据库健康检查端点
echo "=== 开始模拟数据库连接故障 ==="
# 1. 使用iptables阻断PHP应用节点到数据库的TCP连接(注意:这会暂时影响所有服务)
# 这里模拟更安全的“连接池打满”方式:发送大量查询占用连接
# 实际生产建议使用tc或iptables模拟网络分区
echo "通过iptables阻断数据库端口..."
sudo iptables -A OUTPUT -d $DB_HOST -p tcp --dport $DB_PORT -j DROP
# 2. 观测应用行为
echo "等待5秒,观察应用降级/熔断日志..."
sleep 5
db_status=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_CHECK_URL)
echo "数据库故障后的健康检查: HTTP $db_status"
# 3. 清除故障(iptables)
echo "移除iptables规则..."
sudo iptables -D OUTPUT -d $DB_HOST -p tcp --dport $DB_PORT -j DROP
# 4. 验证自愈:等待连接池重建
echo "等待连接池恢复(10秒)..."
sleep 10
final_status=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_CHECK_URL)
echo "恢复后状态: HTTP $final_status"
# 5. 高级验证:检查业务日志中的重试和恢复记录
php_runtime_log="/var/log/php/error.log"
echo "检查PHP日志中是否有重试/恢复记录..."
grep -i "db.*reconnect\|connection.*restored\|circuit breaker.*half-open" $php_runtime_log | tail -5
if [ "$final_status" -eq 200 ]; then
    echo "✅ 数据库故障自愈验证通过"
else
    echo "❌ 服务未完全自愈,请检查连接池和熔断器配置"
fi

模拟Redis缓存故障(缓存降级验证)

<?php
// simulate_redis_failure.php  (作为PHP脚本执行)
// 用法: php simulate_redis_failure.php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 2.5);  // 超时2.5秒
try {
    // 模拟故障:执行一个阻塞命令或故意关闭连接
    echo "正在通过CONFIG SET模拟Redis不可用...\n";
    $redis->rawCommand('DEBUG', 'SEGFAULT');  // 实际会导致Redis崩溃(慎用!)
    // 更安全的模拟方式是:通过系统命令在外部暂停Redis
    // exec("sudo systemctl stop redis-server");
} catch (Exception $e) {
    echo "检测到Redis故障: " . $e->getMessage() . "\n";
    echo "期望行为:应用应降级到直接从数据库读取,不返回500\n";
}
// 模拟验证应用层降级逻辑
$fallback_triggered = false;
try {
    $result = $redis->get('some_key');
    // 如果成功,说明故障未生效(或重连成功)
    echo "警告:Redis仍然可用(故障注入可能未生效)\n";
} catch (Exception $e) {
    $fallback_triggered = true;
    // 应用应在此处执行降级策略:查数据库、返回缓存失效标记、或者记录日志
    echo "降级逻辑触发:从数据库读取替代数据\n";
    $fallback_triggered = true;
}
if ($fallback_triggered) {
    echo "✅ Redis降级自愈逻辑验证通过\n";
    exit(0);
} else {
    echo "❌ 降级逻辑未触发,请检查缓存层故障处理代码\n";
    exit(1);
}

演练的执行与断言

建议使用编排工具(如 makeansible-playbookchaosblade)按顺序执行:

# chaos_test.yml (Ansible格式示例)
- name: 执行故障注入与自愈验证
  hosts: app_servers
  tasks:
    - name: 1. 注入PHP-FPM进程故障
      script: simulate_php_fpm_crash.sh
      register: fpm_result
    - name: 2. 注入DB网络故障
      script: simulate_db_failure.sh
      register: db_result
    - name: 3. 验证业务关键指标
      uri:
        url: "http://{{ inventory_hostname }}/health"
        return_content: yes
      register: health
      until: health.status == 200
      retries: 12
      delay: 10
    - name: 4. 输出断言结果
      assert:
        that:
          - "'OK' in health.content"
          - "'database_connected' in health.content"
        fail_msg: "自愈验证失败:关键健康指标缺失"

自愈能力验证的关键断言点

故障类型 合格的自愈行为 失败信号
PHP-FPM崩溃(进程) 系统自动重启,60s内恢复 5xx持续,监听端口未恢复
数据库断连(网络) 连接池自动重连,熔断器半开 数据库永久错误、慢查询堆积
Redis不可用(缓存) 降级到数据库,响应慢但正常 直接返回500或全链路超时
磁盘写满(存储) 触发日志轮转,写入失败但服务不崩 无法写入临时文件,session错误
高并发(CPU/内存) 限流降级,返回503而非超时 OOM后无恢复,进程被反复kill

特别提示(生产环境演练)

  1. 不要使用 DEBUG SEGFAULT:这会真正导致Redis进程崩溃,请使用 iptablestc 模拟网络不可达。
  2. 注入速率限制:所有故障注入脚本前加入 timeout 30,防止长期不可用。
  3. 记录“红绿灯”指标:演练期间,持续监控 upstream_response_timeerror_ratephp_workers_busy
  4. 回滚脚本:必须有配套的恢复脚本,iptables -Fsystemctl restart $SERVICE

通过这种方式,您可以在受控环境中系统性地验证PHP应用是否具备进程守护连接池重建缓存降级熔断恢复这四大自愈能力。

抱歉,评论功能暂时关闭!