PHP 怎么preStop Hook

wen PHP项目 3

本文目录导读:

PHP 怎么preStop Hook

  1. 使用 pcntl_signal(信号处理)
  2. 使用 register_shutdown_function
  3. 使用 tick 函数
  4. Docker/Kubernetes 中的 preStop 实现
  5. 使用第三方库
  6. 完整的 preStop Hook 示例
  7. 使用建议

在 PHP 中并没有像 Kubernetes 那样的原生 preStop Hook 机制,但我们可以通过多种方式实现类似的功能,以下是几种常见的实现方案:

使用 pcntl_signal(信号处理)

<?php
// 注册信号处理器
pcntl_async_signals(true); // 开启异步信号处理
// 定义清理函数
function cleanup() {
    echo "执行清理操作...\n";
    // 关闭数据库连接
    // 保存状态
    // 清理临时文件等
    file_put_contents('/tmp/app.log', "应用正在停止...\n", FILE_APPEND);
}
// 注册 SIGTERM 信号处理器
pcntl_signal(SIGTERM, function($signo) {
    cleanup();
    exit(0);
});
// 注册 SIGINT (Ctrl+C) 信号处理器
pcntl_signal(SIGINT, function($signo) {
    cleanup();
    exit(0);
});
// 主业务逻辑
echo "应用启动...\n";
while (true) {
    echo "运行中... " . date('Y-m-d H:i:s') . "\n";
    sleep(1);
}
?>

使用 register_shutdown_function

<?php
// 注册关机函数
register_shutdown_function(function() {
    echo "执行关机清理...\n";
    // 清理资源
    file_put_contents('/tmp/app.log', "应用关闭清理\n", FILE_APPEND);
});
// 注册信号处理
pcntl_signal(SIGTERM, function($signo) {
    echo "收到停止信号\n";
    exit(0);
});
pcntl_signal(SIGINT, function($signo) {
    echo "收到中断信号\n";
    exit(0);
});
echo "应用启动...\n";
while (true) {
    sleep(1);
}
?>

使用 tick 函数

<?php
// 使用 tick 来定期检查
declare(ticks=1);
$running = true;
function checkSignal() {
    global $running;
    // 检查是否有停止信号
    if (file_exists('/tmp/stop.signal')) {
        $running = false;
        echo "检测到停止信号\n";
        // 执行清理
        cleanup();
    }
}
function cleanup() {
    echo "执行清理代码...\n";
    // 清理临时文件
    @unlink('/tmp/app.tmp');
    @unlink('/tmp/stop.signal');
}
while ($running) {
    // 业务逻辑
    echo "运行中...\n";
    sleep(1);
    checkSignal();
}
?>

Docker/Kubernetes 中的 preStop 实现

Dockerfile 方式

FROM php:8.1-fpm
# 复制启动脚本
COPY start.sh /usr/local/bin/start.sh
RUN chmod +x /usr/local/bin/start.sh
# 复制停止脚本
COPY preStop.sh /usr/local/bin/preStop.sh
RUN chmod +x /usr/local/bin/preStop.sh
# 设置入口点
ENTRYPOINT ["/usr/local/bin/start.sh"]

preStop.sh

#!/bin/bash
echo "执行 preStop 清理..."
# 发送信号给 PHP-FPM 进程
kill -TERM $(pgrep -f "php-fpm: master")
# 等待进程结束
wait $(pgrep -f "php-fpm: master") 2>/dev/null
echo "清理完成..."

Kubernetes 配置方式

apiVersion: v1
kind: Pod
metadata:
  name: php-app
spec:
  containers:
  - name: php-app
    image: my-php-app
    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sh
          - -c
          - |
            echo "executing preStop hook"
            curl -X POST http://localhost:8080/api/cleanup || true
            ls -la /tmp
            # 等待业务完成
            sleep 5

使用第三方库

Spatie/Async

<?php
use Spatie\Async\Pool;
$pool = Pool::create();
// 添加多个异步任务
foreach ($tasks as $task) {
    $pool[] = async(function() use ($task) {
        // 业务逻辑
        return $task->execute();
    })->then(function($output) {
        // 成功处理
    })->catch(function($exception) {
        // 错误处理
    });
}
// 注册关机钩子
register_shutdown_function(function() use ($pool) {
    echo "停止异步任务...\n";
    $pool->stop();
    echo "异步任务已停止\n";
});
// 等待所有任务完成
$pool->wait();
?>

完整的 preStop Hook 示例

<?php
class PreStopHook {
    private $config;
    private $isStopped = false;
    public function __construct($config = []) {
        $this->config = $config;
        $this->registerSignalHandlers();
        $this->registerShutdownHandler();
    }
    private function registerSignalHandlers() {
        // 异步信号处理(PHP 7.1+)
        pcntl_async_signals(true);
        // 处理 SIGTERM
        pcntl_signal(SIGTERM, [$this, 'handleStop']);
        // 处理 SIGINT
        pcntl_signal(SIGINT, [$this, 'handleStop']);
        // 处理 SIGQUIT
        pcntl_signal(SIGQUIT, [$this, 'handleStop']);
    }
    private function registerShutdownHandler() {
        register_shutdown_function([$this, 'cleanup']);
    }
    public function handleStop($signo) {
        if ($this->isStopped) {
            return;
        }
        $this->isStopped = true;
        $signalName = $this->getSignalName($signo);
        echo "收到信号: {$signalName}\n";
        // 执行停止前的操作
        $this->preStopTasks();
        // 退出
        exit(0);
    }
    private function preStopTasks() {
        echo "执行 preStop 清理任务...\n";
        // 1. 保存状态
        $this->saveState();
        // 2. 清理缓存
        $this->clearCache();
        // 3. 关闭连接
        $this->closeConnections();
        // 4. 删除临时文件
        $this->cleanTempFiles();
        echo "preStop 清理完成\n";
    }
    private function saveState() {
        echo "保存应用状态...\n";
        file_put_contents('/tmp/app-state.json', json_encode([
            'time' => date('Y-m-d H:i:s'),
            'memory' => memory_get_usage(),
            'pid' => getmypid()
        ]));
    }
    private function clearCache() {
        echo "清理缓存...\n";
        // 清理 APCu 缓存
        if (function_exists('apcu_clear_cache')) {
            apcu_clear_cache();
        }
        // 清理 OpCache
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }
    }
    private function closeConnections() {
        echo "关闭数据库连接...\n";
        // 假设使用 PDO
        // $db = null;
    }
    private function cleanTempFiles() {
        echo "清理临时文件...\n";
        array_map('unlink', glob('/tmp/app-*.tmp'));
    }
    private function getSignalName($signo) {
        $signals = [
            SIGTERM => 'SIGTERM',
            SIGINT => 'SIGINT',
            SIGQUIT => 'SIGQUIT'
        ];
        return isset($signals[$signo]) ? $signals[$signo] : "Unknown({$signo})";
    }
    public function run() {
        echo "应用运行中...\n";
        while (!$this->isStopped) {
            // 业务逻辑
            echo "处理业务..." . date('H:i:s') . "\n";
            sleep(1);
        }
    }
}
// 使用示例
$app = new PreStopHook();
$app->run();
?>

使用建议

  1. 选择合适的方式:根据你的部署环境选择最适合的方案
  2. 设置超时:在清理操作中设置超时,避免无限等待
  3. 记录日志:记录清理操作的日志
  4. 错误处理:清理操作中的错误不应该影响正常退出
  5. 测试:充分测试各种停止场景

这些方案都能实现类似 preStop Hook 的功能,选择哪种取决于你的具体需求和使用环境。

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