本文目录导读:

- 场景一:CLI模式下的脚本(如消费队列)
- 场景二:FPM模式下(不推荐,但可实现)
- 场景三:使用异步处理框架(如Swoole/Workerman)
- 场景四:使用进程锁或信号量
- 场景五:使用队列系统(最佳实践)
- 关键建议
- 完整示例(CLI模式)
在PHP项目中,处理“存量请求等待处理完毕再退出”通常有几种常见场景和解决方案,下面我会分场景说明:
CLI模式下的脚本(如消费队列)
<?php
// 优雅退出示例
declare(ticks = 1);
// 定义退出信号处理
pcntl_signal(SIGTERM, function ($signal) {
echo "收到退出信号,等待当前请求处理完毕...\n";
// 设置标志位
global $shouldExit;
$shouldExit = true;
});
$shouldExit = false;
while (true) {
// 检查是否需要退出
if ($shouldExit) {
echo "正在停止接收新任务...\n";
break;
}
// 处理业务逻辑
$request = getNextRequest(); // 获取下一个请求
if ($request) {
processRequest($request); // 处理请求
} else {
sleep(1); // 没有请求时等待
}
}
// 清理资源
cleanupResources();
echo "程序已安全退出\n";
FPM模式下(不推荐,但可实现)
<?php
// 使用register_shutdown_function
register_shutdown_function(function () {
// 确保所有输出已经发送
if (ob_get_level() > 0) {
ob_flush();
flush();
}
// 等待某些异步操作完成
while (hasPendingOperations()) {
usleep(100000); // 100ms
processPendingOperations();
}
});
// 正常业务逻辑
$response = handleRequest();
echo $response;
// 强制等待(不推荐)
sleep(5); // 等待5秒,确保所有请求处理完毕
使用异步处理框架(如Swoole/Workerman)
<?php
// 使用Swoole的优雅退出
$server = new Swoole\Http\Server("0.0.0.0", 9501);
$server->on('request', function ($request, $response) {
// 处理请求
$response->end("Hello World");
});
// 监听进程退出信号
$server->on('shutdown', function ($server) {
echo "正在等待所有请求处理完毕...\n";
// 等待事件循环中的任务完成
while ($server->stats()['task_ing_num'] > 0) {
sleep(1);
}
echo "所有请求已处理完毕\n";
});
$server->start();
使用进程锁或信号量
<?php
// 使用文件锁实现
$lockFile = '/tmp/my_process.lock';
$lockHandle = fopen($lockFile, 'w+');
if (flock($lockHandle, LOCK_EX | LOCK_NB)) {
// 获取到锁,处理请求
// 注册退出函数
register_shutdown_function(function () use ($lockHandle) {
// 释放锁
flock($lockHandle, LOCK_UN);
fclose($lockHandle);
});
// 处理请求
sleep(10); // 模拟长时间处理
echo "请求处理完成\n";
} else {
echo "另一个进程正在处理请求\n";
exit(1);
}
使用队列系统(最佳实践)
<?php
// Redis队列消费示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 退出标志
$shouldExit = false;
// 注册信号处理
pcntl_signal(SIGTERM, function () use (&$shouldExit) {
$shouldExit = true;
});
while (true) {
// 检查退出标志
if ($shouldExit && $redis->llen('task_queue') == 0) {
echo "队列已空,安全退出\n";
break;
}
// 从队列获取任务
$task = $redis->brpop('task_queue', 1);
if ($task) {
processTask($task[1]); // 处理任务
}
}
$redis->close();
关键建议
- 使用信号处理:在CLI脚本中使用
pcntl_signal捕获退出信号 - 设置标志位:收到退出信号后设置标志,循环检查
- 等待队列清空:在处理完所有存量请求后才真正退出
- 超时机制:设置最大等待时间,避免无限等待
- 日志记录:记录开始等待和实际退出的时间
完整示例(CLI模式)
<?php
/**
* 优雅退出的工作进程
*/
class GracefulWorker
{
private $shouldExit = false;
private $queue = [];
private $processing = [];
public function __construct()
{
// 注册信号处理
pcntl_signal(SIGTERM, [$this, 'handleSignal']);
pcntl_signal(SIGINT, [$this, 'handleSignal']);
// 注册退出函数
register_shutdown_function([$this, 'onShutdown']);
}
public function handleSignal($signal)
{
echo "\n收到信号 $signal,准备优雅退出...\n";
$this->shouldExit = true;
}
public function onShutdown()
{
// 清理资源
$this->cleanup();
echo "程序已安全退出\n";
}
public function run()
{
while (true) {
// 分发信号
pcntl_signal_dispatch();
// 检查是否需要退出
if ($this->shouldExit && empty($this->processing)) {
echo "所有请求已处理完毕,退出...\n";
break;
}
// 获取新请求
if (!$this->shouldExit) {
$request = $this->getNextRequest();
if ($request) {
$this->processRequest($request);
}
}
// 处理已完成的请求
$this->handleCompletedRequests();
// 短暂休眠
usleep(100000); // 100ms
}
}
private function getNextRequest()
{
// 模拟获取请求
static $count = 0;
if ($count < 10) {
$count++;
return [
'id' => $count,
'data' => "Request #$count"
];
}
return null;
}
private function processRequest($request)
{
echo "处理请求: {$request['id']}\n";
$this->processing[$request['id']] = $request;
// 模拟处理时间
sleep(rand(1, 3));
unset($this->processing[$request['id']]);
echo "请求 {$request['id']} 处理完成\n";
}
private function handleCompletedRequests()
{
// 模拟检查已完成的请求
}
private function cleanup()
{
echo "清理资源...\n";
}
}
// 运行工作进程
$worker = new GracefulWorker();
$worker->run();
这个示例实现了:
- 信号捕获(SIGTERM/SIGINT)
- 优雅退出(等待所有请求处理完毕)
- 资源清理
- 状态检查
根据你的具体使用场景(CLI脚本、Web请求、长连接服务等),选择最适合的实现方式。