Laravel取消用信号还是标志

wen PHP项目 16

本文目录导读:

Laravel取消用信号还是标志

  1. 为什么“信号”优于“标志”
  2. Laravel 中如何实现信号取消?
  3. 如果必须使用“标志”(不推荐但可行)
  4. 推荐做法总结
  5. 在 Laravel Horizon / 队列中的应用

在 Laravel 中取消长时间运行的任务(如队列、命令、循环处理),通常推荐使用“信号”(Signal)机制,而不是“标志”(比如数据库字段、缓存键、文件锁),原因如下:

为什么“信号”优于“标志”

方面 信号(Signal) 标志(Flag,如 DB/cache)
实时性 操作系统随时中断进程,立即响应 需循环轮询,有延迟(取决于查询间隔)
资源开销 零开销,由内核处理 每次轮询都有 I/O 或网络请求
可靠性 进程退出/信号量/释放锁等,天然安全 可能因进程异常退出导致标志残留
适用场景 守护进程、CLI 命令、队列 worker Web 请求中简单轮询控制(如进度条)

Laravel 中如何实现信号取消?

场景:一个后台运行的 Artisan 命令

namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LongRunningCommand extends Command
{
    protected $signature = 'app:long-run';
    protected $description = '一个可以被取消的长时间任务';
    public function handle()
    {
        $this->line("PID: " . getmypid());
        $this->line("要取消,请运行: kill -SIGTERM " . getmypid());
        // 注册信号处理
        $this->trap('SIGTERM', function () {
            $this->line('收到 SIGTERM,准备优雅退出...');
            // 此处可以清理资源、保存进度等
            exit(0); // 或设置一个终止标志供循环检查
        });
        $this->trap('SIGINT', function () {
            $this->line('收到 Ctrl+C (SIGINT),退出...');
            exit(0);
        });
        $shouldStop = false; // 供循环检查
        // 真正的循环任务
        for ($i = 0; $i < 1000 && !$shouldStop; $i++) {
            // 模拟工作
            sleep(1);
            $this->line("处理第 {$i} 项...");
            // 如果在上面的 trap 中设置了 $shouldStop = true,这里就会跳出
        }
        $this->line("任务结束或已取消");
    }
}

更优雅的方式:使用 $this->shouldStop() 方法

Laravel 的命令基类内置了对 SIGTERM 的支持,你只需要在循环中检查 $this->shouldStop() 即可:

public function handle()
{
    $this->trap('SIGTERM', fn() => $this->shouldKeepRunning = false);
    $this->trap('SIGINT',  fn() => $this->shouldKeepRunning = false);
    while ($this->shouldKeepRunning) {
        // 你的业务逻辑
    }
}

如果必须使用“标志”(不推荐但可行)

适用于:前台 Web 用户控制任务暂停/继续,且无法直接发送 OS 信号给进程。

// 定义标志(Redis or DB)
// 用户点击取消时,将标志设为 true
// 在循环中轮询
while (true) {
    if (Cache::get('task_cancel_flag_' . $taskId)) {
        break; // 取消
    }
    // 执行业务
    sleep(2); // 轮询间隔不宜过短
}

缺点

  • 用户取消后,可能仍需等待 0~2 秒(轮询间隔)。
  • 进程崩溃后标志永远为 true,需手动清理。

推荐做法总结

场景 推荐方式
控制台命令/队列 worker 取消 信号(Signal)
长时间 web 请求(如导出大文件) 定期检查 connection->aborted()
多 worker 间共享取消状态(如分布式任务) 标志(Redis/Database) + 心跳监控

在 Laravel Horizon / 队列中的应用

如果你使用 Horizon 运行队列,可以直接用 pcntl_signalSIGTERM 进行处理,Horizon 本身在收到 SIGQUIT / SIGTERM 时会优雅停止 worker。

// 在 Horizon 的 Job 中(不推荐,较复杂)
// 推荐在 Supervisor 层面控制 worker 重启/停止

首选信号,因为它是操作系统层面最直接、最高效的取消机制,仅在无法获取进程控制权(如 Web 请求中)或需要跨进程共享取消状态时,才考虑使用标志。

如果你需要示例代码或对信号处理在 Laravel 中的具体坑点(pcntl 扩展要求)感兴趣,我可以进一步说明。

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