PHP项目存量请求如何等待处理完毕再退出

wen PHP项目 27

本文目录导读:

PHP项目存量请求如何等待处理完毕再退出

  1. 场景一:CLI模式下的脚本(如消费队列)
  2. 场景二:FPM模式下(不推荐,但可实现)
  3. 场景三:使用异步处理框架(如Swoole/Workerman)
  4. 场景四:使用进程锁或信号量
  5. 场景五:使用队列系统(最佳实践)
  6. 关键建议
  7. 完整示例(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();

关键建议

  1. 使用信号处理:在CLI脚本中使用 pcntl_signal 捕获退出信号
  2. 设置标志位:收到退出信号后设置标志,循环检查
  3. 等待队列清空:在处理完所有存量请求后才真正退出
  4. 超时机制:设置最大等待时间,避免无限等待
  5. 日志记录:记录开始等待和实际退出的时间

完整示例(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请求、长连接服务等),选择最适合的实现方式。

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