PHP项目健康检查怎么做

wen PHP项目 3

本文目录导读:

PHP项目健康检查怎么做

  1. 健康检查的核心层级
  2. 基础方案:简单脚本(适用于小型项目)
  3. 进阶方案:结构化健康检查(推荐)
  4. 线上生产环境的注意事项
  5. 进阶场景:深度业务检查(Deep Health)
  6. 监控集成建议
  7. 完整代码示例(原生PHP,无框架)
  8. 总结建议

在 PHP 项目中实施健康检查,关键在于分层设计按需求关注点来构建,下面我从基础到进阶,结合实战代码,为你梳理一套完整的健康检查方案。


健康检查的核心层级

健康检查分为三个层次,你可以根据项目规模选择:

层级 目的 触发频率
Liveness 进程是否存活、PHP-FPM是否响应 确认服务没死 高频(每10秒)
Readiness 数据库连接、Redis、外部API连通性 确认服务能否处理请求 中频(每30秒)
Deep Health 核心业务逻辑(如订单写入测试)、磁盘IO、队列堆积情况 确认“业务”是否健康 低频(每分钟)

核心原则必须使用HTTP接口返回JSON,并配合正确的HTTP状态码(200/503)。


基础方案:简单脚本(适用于小型项目)

这是最快速的实现方式,直接创建一个路由或脚本文件。

创建检查文件 health.php(放置在Web根目录):

<?php
// 简单的健康检查:只检查PHP环境和应用是否能引导起来
require_once __DIR__ . '/vendor/autoload.php'; // 引入启动文件(如果有)
$health = [
    'status' => 'ok',
    'time'   => date('c'),
    'checks' => []
];
// 检查1:PHP版本
$health['checks']['php_version'] = phpversion();
// 检查2:类是否加载成功(如果框架核心类加载不了,这里会直接500错误)
if (!class_exists('YourFramework\Application')) {
    http_response_code(503);
    $health['status'] = 'error';
    echo json_encode($health);
    exit;
}
if ($health['status'] === 'ok') {
    http_response_code(200);
} else {
    http_response_code(503);
}
header('Content-Type: application/json');
echo json_encode($health);

缺点:只能检测应用能否启动,无法检测依赖服务(DB/Redis)是否可用。


进阶方案:结构化健康检查(推荐)

此方案关注 ReadinessDeep Health,适合中大型项目。

步骤 1:定义检查器接口

<?php
// app/Contracts/HealthCheckInterface.php
interface HealthCheckInterface
{
    /**
     * 执行健康检查
     * @return array ['status' => 'ok'|'error', 'details' => '附加信息(如延迟ms)']
     */
    public function check(): array;
}

步骤 2:实现具体检查类

示例A:数据库连接检查(MySQL / PostgreSQL)

<?php
// app/HealthChecks/DatabaseHealthCheck.php
class DatabaseHealthCheck implements HealthCheckInterface
{
    protected $pdo;
    public function __construct($host, $port, $name, $user, $pass) 
    {
        // 注意:这里使用真实的短时连接,避免占用连接池
        $this->pdo = new PDO("mysql:host=$host;port=$port;dbname=$name", $user, $pass, [
            PDO::ATTR_TIMEOUT => 2 // 设置2秒超时
        ]);
    }
    public function check(): array
    {
        $start = microtime(true);
        try {
            $this->pdo->query('SELECT 1');
            return [
                'status' => 'ok', 
                'details' => 'Connection successful ('. (microtime(true) - $start) .'s)'
            ];
        } catch (\Throwable $e) {
            return ['status' => 'error', 'details' => $e->getMessage()];
        }
    }
}

示例B:Redis / 缓存检查

<?php
class RedisHealthCheck implements HealthCheckInterface
{
    protected $redis;
    public function __construct(\Redis $redis) 
    {
        $this->redis = $redis;
    }
    public function check(): array
    {
        try {
            $this->redis->ping(); // PING命令
            return ['status' => 'ok', 'details' => 'PING success'];
        } catch (\Throwable $e) {
            return ['status' => 'error', 'details' => $e->getMessage()];
        }
    }
}

步骤 3:核心调度器(汇总 + 状态码判断)

<?php
// app/HealthCheck/Manager.php
class HealthCheckManager
{
    protected $checks = [];
    protected $criticalChecks = ['database', 'redis']; // 哪些是必须健康的?
    public function addCheck(string $name, HealthCheckInterface $check, bool $isCritical = true)
    {
        $this->checks[$name] = ['check' => $check, 'critical' => $isCritical];
        return $this;
    }
    public function diagnose(string $type = 'readiness'): array
    {
        $results = [];
        $overallStatus = 'ok';
        foreach ($this->checks as $name => $item) {
            // 对于Liveness检查,只检查主进程
            if ($type === 'liveness' && $name !== 'app_status') {
                continue;
            }
            $result = $item['check']->check();
            $results[$name] = $result;
            // 关键检查失败 -> 整体状态设为error
            if ($result['status'] === 'error' && $item['critical']) {
                $overallStatus = 'error';
            }
        }
        return [
            'status' => $overallStatus,
            'checks' => $results,
            'timestamp' => time(),
            'uptime' => (time() - $_SERVER['REQUEST_TIME_FLOAT'])
        ];
    }
}

步骤 4:统一入口 Controller / Router

<?php
// routes/web.php (Laravel) 或 自定义路由
Route::get('/health/liveness', function () {
    // 简化版:只要PHP进程活着,就返回200
    return response()->json(['status' => 'ok']);
});
Route::get('/health/readiness', function () {
    $manager = new HealthCheckManager();
    // 注册依赖
    $manager->addCheck('database', new DatabaseHealthCheck(env('DB_HOST'), env('DB_PORT'), env('DB_DATABASE'), env('DB_USERNAME'), env('DB_PASSWORD')));
    $manager->addCheck('redis', new RedisHealthCheck(Redis::connection()->client()));
    // 执行诊断
    $result = $manager->diagnose('readiness');
    // 关键:根据状态返回HTTP码
    $statusCode = ($result['status'] === 'ok') ? 200 : 503;
    return response()->json($result, $statusCode);
});

线上生产环境的注意事项

两个必做

  • 禁用缓存:健康检查接口必须跳过所有业务缓存(包括OPcache、响应缓存),确保结果是实时的。
  • 超时控制:所有连接必须设置 connect_timeout(建议 2-3秒),防止某个依赖服务“假死”导致健康检查接口本身也超时挂起。

三种特殊场景

  • 数据库连接池:如果你的项目用了连接池(如Swoole),健康检查不要拿连接池里的连接去 SELECT 1,因为可能会取到已断连的旧连接,应该新建一个临时连接进行测试,用完立即销毁。
  • 负载均衡器:如果有多台服务器,确保每台机器的健康检查都有独立的随机数参数(如 ?ts=1641024000),避免CDN缓存返回旧结果。
  • 只读端点:健康检查的URL应该完全公开,不要求JWT或Session认证,确保运维工具(如Prometheus、K8s探针)能直接访问。

日志记录

在健康检查失败时,不要将大量异常堆栈打印到应用日志(避免攻击者利用/日志爆炸),只记录错误码和简要信息:

// 在Manager diagnose方法中
if ($result['status'] === 'error') {
    \Log::warning('Health Check Failed', [
        'service' => $name,
        'error' => (substr($result['details'], 0, 40)) // 截取前40字符
    ]);
}

进阶场景:深度业务检查(Deep Health)

当你的应用依赖大量中间件(消息队列、第三方支付)时,只查数据库往往不够。

示例:检查消息队列堆积是否超标

class QueueHealthCheck implements HealthCheckInterface
{
    protected $redis;
    public function check(): array
    {
        $queueLength = $this->redis->lLen('jobs_queue');
        // 如果队列超过10000条,则视为“亚健康”
        if ($queueLength > 10000) {
            return [
                'status' => 'error',
                'details' => "Queue too long: {$queueLength}"
            ];
        }
        return ['status' => 'ok', 'details' => "Queue length: {$queueLength}"];
    }
}

监控集成建议

如果你的健康检查接口已经返回JSON,推荐用以下工具采集:

  1. Kubernetes (K8s):配置 livenessProbe 指向 /health/livenessreadinessProbe 指向 /health/readiness
  2. Prometheus:使用 php_exporter 结合 curl 检查 /health/readiness 的 HTTP 状态码。
  3. 阿里云/腾讯云 SLB:配置 TCP 监听检查 + HTTP 请求检查。

完整代码示例(原生PHP,无框架)

如果你的项目没有框架,这个文件可以单独放在 public/ 目录下:

<?php
// health.php
header('Content-Type: application/json');
$response = ['status' => 'ok', 'timestamp' => time()];
// 1. 终极简单检查(Liveness)
if (php_sapi_name() === 'cli') {
    // 命令行模式下无HTTP响应,直接退出
    exit(0);
}
// 2. 检查数据库(Readiness)
try {
    $pdo = new PDO('mysql:host=127.0.0.1;port=3306;dbname=app', 'user', 'pass', [
        PDO::ATTR_TIMEOUT => 2
    ]);
    $pdo->query('SELECT 1');
    $response['checks']['database'] = 'ok';
} catch (\Exception $e) {
    $response['status'] = 'error';
    $response['checks']['database'] = $e->getMessage();
}
// 3. 检查磁盘写权限(对临时目录)
$testFile = sys_get_temp_dir() . '/health_' . uniqid() . '.txt';
if (@file_put_contents($testFile, 'test') === false) {
    $response['status'] = 'error';
    $response['checks']['disk'] = 'not writable';
} else {
    unlink($testFile);
    $response['checks']['disk'] = 'ok';
}
// 输出状态码
if ($response['status'] === 'error') {
    http_response_code(503);
}
echo json_encode($response);

总结建议

  1. 小项目:用脚本检测核心依赖连接(基础方案即可)。
  2. 中大型项目:按 Liveness / Readiness / Deep Health 分层设计接口。
  3. 避免过度复杂:健康检查本身不应消耗过多资源,超时设置要短(3秒内),频率要合理。

建议你的健康检查接口返回结果包含 versioncommit_hash,方便排查“哪次发布导致不健康”,这在排查问题时会很有帮助。

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