如何用PHP项目实现健康检查?——从零搭建高可用监控体系
目录导读
- 为什么要为PHP项目做健康检查?
- 健康检查的核心原理与类型
- 实战:PHP健康检查接口代码实现
- 进阶:数据库与外部服务依赖检查
- 集成到负载均衡与容器编排(Docker/K8s)
- 常见问答FAQ
- 总结与最佳实践
为什么要为PHP项目做健康检查?
在微服务架构、容器化部署(Docker/Kubernetes)以及高可用集群环境中,PHP应用(无论是传统的Laravel、Symfony,还是轻量的Slim框架)都需要一个“心跳”机制来告知基础设施:我还活着,并且功能正常。

核心痛点:当PHP-FPM进程死锁、数据库连接池耗尽、Redis缓存挂掉,或者磁盘空间满导致session无法写入时,如果没有健康检查,负载均衡器(Nginx/HAProxy)仍然会继续向有问题的实例转发请求,导致用户看到500错误或页面卡死。
健康检查的价值:
- 自动摘除故障节点,保障服务SLA
- 配合容器编排工具实现自动重启
- 为监控系统(Prometheus/Grafana)提供数据源
健康检查的核心原理与类型
1 两种主流健康检查方式
| 类型 | 协议 | 典型用法 | 优点 | 缺点 |
|---|---|---|---|---|
| Liveness Probe(存活检查) | HTTP/HTTPS | 判断服务是否在运行 | 快速识别进程死锁 | 可能误报(GC暂停时) |
| Readiness Probe(就绪检查) | HTTP/HTTPS | 判断服务是否可以接收流量 | 避免流量发给不可用节点 | 需要更多依赖检查 |
2 PHP项目健康检查的层级
一个完善的健康检查应该至少覆盖以下三层:
- 应用层:PHP解释器正常运行,能响应HTTP请求
- 数据层:MySQL/PostgreSQL连接正常
- 缓存层:Redis/Memcached可用性
- 依赖服务:第三方API可达性(可选)
⚠️ 重要原则:健康检查接口本身必须轻量级,避免复杂业务逻辑,检查类如检查数据库,使用
SELECT 1而非全表扫描。
实战:PHP健康检查接口代码实现
1 基础健康检查(纯PHP)
创建一个health.php文件,放在项目public目录下(或配置路由):
<?php
// health.php
header('Content-Type: application/json');
header('Cache-Control: no-cache, no-store, must-revalidate');
$healthStatus = [
'status' => 'ok',
'timestamp' => date('c'),
'version' => '1.0.0',
'php_version' => phpversion(),
'memory_usage' => round(memory_get_usage(true)/1024/1024, 2) . ' MB',
'uptime' => exec('uptime -s') ?: 'unknown'
];
http_response_code(200);
echo json_encode($healthStatus, JSON_PRETTY_PRINT);
关键点:
- 返回HTTP 200表示正常
- 包含时间戳让监控系统能判断数据新鲜度
- 禁止缓存,避免CDN等中间层误报
2 使用框架路由(以Laravel为例)
// routes/api.php
Route::get('/health', function () {
try {
DB::connection()->getPdo();
return response()->json([
'status' => 'healthy',
'database' => 'connected',
'app_env' => config('app.env'),
], 200);
} catch (\Exception $e) {
return response()->json([
'status' => 'unhealthy',
'error' => $e->getMessage()
], 503);
}
});
进阶:数据库与外部服务依赖检查
1 多依赖检查的优雅方案
当项目依赖多个服务时,我们不应该因为Redis挂了就整体标记为unhealthy,建议采用“依赖分级”策略:
<?php
// health_check.php
class HealthChecker {
private array $checks = [];
private array $results = [];
public function addCheck(string $name, callable $check, bool $critical = true): self {
$this->checks[] = compact('name', 'check', 'critical');
return $this;
}
public function run(): array {
$overallStatus = 'healthy';
foreach ($this->checks as $check) {
try {
$result = call_user_func($check['check']);
$this->results[$check['name']] = $result ? 'ok' : 'degraded';
if ($result === false && $check['critical']) {
$overallStatus = 'unhealthy';
}
} catch (\Throwable $e) {
$this->results[$check['name']] = 'error: ' . $e->getMessage();
if ($check['critical']) {
$overallStatus = 'unhealthy';
}
}
}
http_response_code($overallStatus === 'healthy' ? 200 : 503);
return [
'status' => $overallStatus,
'checks' => $this->results,
];
}
}
// 使用示例
$checker = new HealthChecker();
$checker->addCheck('mysql', function() {
return DB::connection()->getPdo()->query('SELECT 1');
}, true); // critical
$checker->addCheck('redis', function() {
return Redis::ping() === 'PONG';
}, false); // non-critical
$checker->addCheck('disk_space', function() {
return disk_free_space('/') > 100 * 1024 * 1024; // 至少100MB
}, true);
return $checker->run();
2 常见依赖检查实现模板
| 依赖类型 | 检查方法 | 超时建议 |
|---|---|---|
| MySQL | SHOW TABLES LIKE 'users' |
2秒 |
| Redis | PING |
1秒 |
| RabbitMQ | 建立AMQP连接并关闭 | 3秒 |
| Elasticsearch | 访问/_cluster/health |
3秒 |
| 外部API | 请求文档头且只获取状态码 | 5秒 |
集成到负载均衡与容器编排(Docker/K8s)
1 Nginx健康检查配置(被动模式)
upstream php_backend {
server 127.0.0.1:9000;
server 127.0.0.1:9001;
# 主动健康检查(需要nginx-plus或开源模块)
health_check uri=/health interval=5s fails=3 passes=2;
# 被动检查
max_fails=3 fail_timeout=30s;
}
server {
location /health {
proxy_pass http://php_backend;
proxy_http_version 1.1;
proxy_cache_bypass 1;
access_log off;
}
}
2 Kubernetes Pod yaml配置
apiVersion: v1
kind: Pod
spec:
containers:
- name: php-app
image: my-php-app:latest
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
重点提示:Liveness和Readiness使用同一个接口但返回不同逻辑,如果只使用一个接口,建议Liveness仅检查进程存活,Readiness检查全部依赖。
常见问答FAQ
Q1: 健康检查接口应该用GET还是POST?
A: 推荐使用GET方法,因为健康检查是幂等操作,且GET请求可以被缓存端跳过,也可以被浏览器直接访问测试,部分安全团队会要求使用HEAD方法以减小响应体积。
Q2: 健康检查是否应该包含认证?
A: 通常不建议包含,因为负载均衡器和K8s不会携带Token,如果一定要保护,可以使用IP白名单(如只允许10.0.0.0/8网段访问)或防火墙规则。
Q3: 为什么我的健康检查偶尔超时导致Pod被重启?
A: 最常见的原因是健康检查接口内部包含了慢查询或第三方调用,解决方案:1) 为每个依赖检查设置独立的超时(使用stream_set_timeout);2) 将非关键检查放在额外接口(如/health/deep)中。
Q4: PHP项目的健康检查应该放在哪个路径?
A: 行业惯例是/health、/healthz或/status,Kubernetes官方示例推荐使用/healthz,保持路径简短且不带版本号(如/v1/health)更佳。
Q5: 如何处理健康检查的日志轰炸?
A: 在Web服务器层面(Nginx/Apache)或PHP应用内部跳过日志记录,例如在Laravel中:
// app/Exceptions/Handler.php
public function shouldntReport(): array
{
return [
\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class,
// 添加健康检查异常排除
];
}
总结与最佳实践
1 核心清单
- ✅ 返回HTTP 200表示正常,503/500表示异常
- ✅ 禁用缓存(
Cache-Control: no-cache) - ✅ 接口执行时间控制在200ms以内
- ✅ 区分Liveness和Readiness的检查粒度
- ✅ 包含版本号和PHP版本信息便于排查
- ✅ 非关键依赖降级为“degraded”而非整体不可用
2 避免的坑
- ❌ 不要在健康检查中执行业务逻辑
- ❌ 不要返回HTML错误页面(保持JSON格式)
- ❌ 不要让健康检查跨越多个时区(统一使用UTC)
- ❌ 不要在容器启动后立即检查(等待应用初始化完毕)
3 推荐扩展工具
- PHP-DI容器健康检查:自动解析依赖关系
- spatie/laravel-health:Laravel生态专用的健康检查包
- Prometheus Exporter:将健康检查结果暴露为Prometheus指标
通过以上方法,你的PHP项目将具备生产级别的高可用自检能力,无论是部署在物理机、Docker还是Kubernetes环境中,都能稳定运行并快速发现问题,从今天起,为你的PHP应用加上健康检查吧——这是最简单却又最有效的运维手段之一。