本文目录导读:

这是一个非常关键的运维与架构设计话题,在PHP项目中,实现健康检查与自愈机制,可以显著提升系统的可用性和稳定性,尤其是在生产环境或容器化部署(如Docker/K8s)中。
下面我从原理、实现层级、具体代码示例以及自愈策略四个方面为你详细拆解。
核心概念
- 健康检查(Health Check):定期检查系统是否能正常提供服务,通常分为两种:
- 存活检查:进程是否还活着?比如端口是否监听,FPM是否运行。
- 就绪检查:服务是否已经准备好接收流量?比如数据库是否连接成功,缓存是否加载完毕。
- 业务检查:核心业务链路是否通畅?比如支付接口、队列消费速度。
- 自愈(Self-Healing):当健康检查发现异常时,系统自动执行恢复操作,无需人工介入。
PHP项目的健康检查实现
最简单的实现:一个独立的 health.php 文件
这是最直接的方式,由负载均衡器或K8s Probe定期请求。
<?php
// health.php - 放到项目根目录,并确保不被路由拦截
header('Content-Type: application/json');
$status = 200;
$errors = [];
// 1. 检查 PHP 本身
if (PHP_SAPI === 'cli') {
// 如果是 CLI 模式检查,忽略
}
// 2. 检查数据库连接 (PDO)
try {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS, [
PDO::ATTR_TIMEOUT => 3, // 3秒超时
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$pdo->query('SELECT 1');
} catch (\Exception $e) {
$errors[] = 'database: ' . $e->getMessage();
}
// 3. 检查 Redis 连接
if (class_exists('\Redis')) {
try {
$redis = new \Redis();
$redis->connect(REDIS_HOST, REDIS_PORT, 2.5); // 超时2.5秒
$redis->ping();
} catch (\Exception $e) {
$errors[] = 'redis: ' . $e->getMessage();
}
}
// 4. 检查磁盘空间 (防止日志写满)
$diskFree = disk_free_space('/');
if ($diskFree !== false && $diskFree < 100 * 1024 * 1024) { // 小于100MB报警
$errors[] = 'disk: low space (' . round($diskFree / 1024 / 1024) . 'MB left)';
}
// 5. 检查队列 (如 Laravel Horizon / ThinkQueue)
// 假设你有队列心跳表或API
// $queueStatus = checkQueueWorker();
// if (!$queueStatus) $errors[] = 'queue: workers not running';
// 输出结果
if (!empty($errors)) {
$status = 503;
http_response_code($status);
echo json_encode([
'status' => 'unhealthy',
'timestamp' => time(),
'errors' => $errors
], JSON_UNESCAPED_UNICODE);
} else {
http_response_code(200);
echo json_encode([
'status' => 'healthy',
'timestamp' => time()
]);
}
exit(0);
更完善的方案:使用框架健康检查包
Laravel 用户强烈建议使用 spatie/laravel-health 或 Laravel Horizon (自带队列检查)。
composer require spatie/laravel-health php artisan vendor:publish --provider="Spatie\Health\HealthServiceProvider" --tag="health-config"
配置后只需访问 /health 路由,即可看到美观的健康仪表盘并支持自定义检查。
容器环境下的健康检查 (Dockerfile)
如果你使用 Docker,建议直接在 Dockerfile 中集成检查命令,而不是在容器内部起一个Web Server。
FROM php:8.2-fpm # 安装依赖... COPY . /var/www/html # 设置健康检查命令 (使用 CURL 或 PHP CLI) # 注意:这里使用的是 DOCKER 的 HEALTHCHECK 指令 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost/health.php || exit 1
在 docker-compose.yml 中也可以配置:
services:
php:
image: my-php-app:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health.php"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s # 给应用缓冲启动时间
机器环境(如 Supervisor):创建一个简单的 check_and_restart.sh 脚本,用 Cron 每分钟调用。
#!/bin/bash
# check_php_service.sh
URL="http://127.0.0.1:8080/health.php"
LOG="/var/log/healthcheck.log"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 10 $URL)
if [ "$HTTP_CODE" != "200" ]; then
echo "$(date) - Health check FAILED (code: $HTTP_CODE). Attempting restart..." >> $LOG
# 重启 PHP-FPM
systemctl restart php8.2-fpm
# 或者重启 Supervisor 管理的进程
# supervisorctl restart all
echo "$(date) - Restart command issued." >> $LOG
else
echo "$(date) - Health check OK." >> $LOG
fi
然后在 /etc/crontab 中添加:
*/1 * * * * root /path/to/check_php_service.sh
自愈策略 (Self-Healing)
健康检查本身只发现问题,自愈才是关键,以下策略从轻到重排列:
进程级自愈(Supervisor / Systemd)
- 原理:PHP-FPM 或 Workerman/Swoole 进程崩溃后,进程管理器立刻拉起新进程。
- 实施:使用
supervisord管理你的 PHP 常驻进程(如队列消费者、Swoole Server)。
[program:php-worker] process_name=%(program_name)s_%(process_num)02d command=php /path/to/artisan queue:work --sleep=3 --tries=3 autostart=true autorestart=true ; 核心:自动重启 user=www-data numprocs=4 ; 多进程 redirect_stderr=true stdout_logfile=/var/log/worker.log
容器级自愈(Docker Restart Policies & K8s)
- Docker:设置
restart: always或restart: unless-stopped。 - Kubernetes:
livenessProbe失败 -> K8s 杀死并重启 Pod。readinessProbe失败 -> K8s 从 Service Endpoint 中移除 Pod(不接收流量,但进程不重启)。startupProbe防止慢启动被误杀。
apiVersion: v1
kind: Pod
metadata:
name: php-app
spec:
containers:
- name: php-app
image: my-php-app:latest
livenessProbe:
httpGet:
path: /health.php
port: 8080
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health.php
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
startupProbe:
httpGet:
path: /health.php
port: 8080
initialDelaySeconds: 3
failureThreshold: 30 # 最多等待 30s
业务逻辑自愈(代码层面)
- 数据库重连机制:
- 连接断开后,不要直接抛异常退出,而是尝试重连 3 次,每次间隔 0.5 秒。
- 很多 ORM(如 Eloquent/Doctrine)自带断线重连配置:
'options' => [ PDO::ATTR_EMULATE_PREPARES => true, 'reconnect' => true ]。
- 队列失败重试:
- 使用 Laravel 队列的
--tries=3参数,失败后自动重试,或进入失败表人工处理。
- 使用 Laravel 队列的
- 请求限流/降级:
当健康检查发现数据库响应慢时,自动切换到缓存读取或返回友好降级页面(而非直接崩溃)。
- 慢查询自动 Kill:
- 定期检查
SHOW FULL PROCESSLIST,找出执行时间过长的 SQL 自动 Kill。
- 定期检查
系统级自愈(硬件/OS)
- 磁盘空间监控:在
health.php中检查disk_free_space,低于阈值时自动清理历史日志文件(如find /var/log -name "*.log" -mtime +7 -delete)。 - 内存超限:PHP-FPM 进程内存超限时,监控脚本自动
kill -9该进程,Supervisor 会自动重启。
常见陷阱与最佳实践
-
不要在生产环境暴露详细的错误信息。
health.php返回的errors数组在外部只保留database: connection failed,不要把密码或 IP 暴露。 -
健康检查路径不能有权限认证(如 Session),否则会导致检查伪失败。
-
健康检查本身不能依赖数据库或 Redis,如果它们挂了,你的健康检查也无法工作(自陷死局),建议只检查最基本的文件系统和进程。
- 推荐方案:健康检查分为两个层级:
- Liveness:只检查进程存在(如
ps aux | grep php-fpm)。 - Readiness:检查数据库等外部依赖。
- Liveness:只检查进程存在(如
- 推荐方案:健康检查分为两个层级:
-
避免健康检查过重,每次检查应在 200ms 内完成,否则会影响整体响应。
-
自愈不能解决所有问题,如果脚本因代码死循环导致内存泄漏,重启后依然会泄漏。自愈是降级手段,最终仍需人工排查根因。
-
日志与告警:健康检查失败时,除了自愈,一定要通过邮件、钉钉、Slack 等方式通知到人。
| 层级 | 检查方式 | 自愈手段 |
|---|---|---|
| 应用代码 | /health.php 路由 |
数据库重连、队列重试、缓存降级 |
| 进程管理 | Supervisor/systemd 状态 | autorestart=true |
| 容器编排 | Docker HEALTHCHECK / K8s Probe | 重启容器 / Pod,转移流量 |
| 基础设施 | 磁盘、内存、CPU | 清理日志、自动扩容、自动迁移 |
建议落地方案:
- 在项目中立即添加一个简单的
health.php,检查基础依赖。 - 如果是 Docker 环境,升级为 K8s Probe + Restart Policy。
- 如果是传统机器,使用 Supervisor 管理进程 + Cron 执行健康检查脚本。
- 加入日志监控(如 Sentry / Prometheus),实现告警链条。
这样,你的 PHP 项目就具备了一定程度的自我诊断和自动恢复能力。