本文目录导读:

在PHP项目中模拟故障并验证自愈能力,通常需要从基础设施层面(如进程、网络、存储)和应用层面(如数据库、缓存、第三方API)两个维度入手,以下是一套可执行的实战演练方案。
核心设计思路
- 可观测性先行:确保演练前已部署日志、指标(如QPS、错误率)和告警。
- 最小影响原则:优先在预发布环境或业务低峰期进行,或使用服务降级后的影子副本。
- 自动化断言:演练结束后自动检查服务是否恢复至预定义的健康基线。
典型故障模拟脚本(Bash + PHP 命令)
以下脚本假设您的PHP服务通过 php-fpm 或 supervisord 管理,并且具备基本的健康检查端点(如 /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);
}
演练的执行与断言
建议使用编排工具(如 make、ansible-playbook 或 chaosblade)按顺序执行:
# 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 |
特别提示(生产环境演练)
- 不要使用
DEBUG SEGFAULT:这会真正导致Redis进程崩溃,请使用iptables或tc模拟网络不可达。 - 注入速率限制:所有故障注入脚本前加入
timeout 30,防止长期不可用。 - 记录“红绿灯”指标:演练期间,持续监控
upstream_response_time、error_rate和php_workers_busy。 - 回滚脚本:必须有配套的恢复脚本,
iptables -F、systemctl restart $SERVICE。
通过这种方式,您可以在受控环境中系统性地验证PHP应用是否具备进程守护、连接池重建、缓存降级和熔断恢复这四大自愈能力。