本文目录导读:

- 使用 pcntl_signal(信号处理)
- 使用 register_shutdown_function
- 使用 tick 函数
- Docker/Kubernetes 中的 preStop 实现
- 使用第三方库
- 完整的 preStop Hook 示例
- 使用建议
在 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();
?>
使用建议
- 选择合适的方式:根据你的部署环境选择最适合的方案
- 设置超时:在清理操作中设置超时,避免无限等待
- 记录日志:记录清理操作的日志
- 错误处理:清理操作中的错误不应该影响正常退出
- 测试:充分测试各种停止场景
这些方案都能实现类似 preStop Hook 的功能,选择哪种取决于你的具体需求和使用环境。