支持Laravel.io的持续发展 →

使用Supervisor运行自定义Artisan命令

2024年2月27日 阅读时间:7分钟

如果您以前在服务器上使用过 Laravel 队列,您可能已在 文档 中看到关于在队列进程因任何原因停止的情况下,需要在您的服务器上运行 Supervisor 以保持队列进程持续的章节。您通常需要做的就是在 配置 Supervisor,以便运行 queue:work Artisan 命令。

现在我不会详细说明关于队列的不同Artisan命令如何在后台运行的细节,但如果我们仔细看看比如 queue:work 命令,基本上正在发生的情况是作业在一个 while loop 中运行,如果满足某些条件,工作进程将被停止,并退出命令。然后Supervisor将会再次运行它。

以下是一个简化后的循环示例(来自 Illuminate/Queue/Worker.php 文件中的 daemon 函数)

while (true) {
    // Get the next job from the queue.
    $job = $this->getNextJob(
        $this->manager->connection($connectionName), $queue
    );

    // Run the job if needed.
    if ($job) {
        $jobsProcessed++;

        $this->runJob($job, $connectionName, $options);
    }

    // Get the worker status.
    $status = $this->stopIfNecessary(
        $options, $lastRestart, $startTime, $jobsProcessed, $job
    );

    // Stop the worker if needed.
    if (! is_null($status)) {
        return $this->stop($status, $options);
    }
}
<h2 class="subheading">自定义命令</h2>

现在,如果我们以这个示例作为起点,我们可以创建一个自己的命令,该命令在需要的情况下可以通过Supervisor无限期地运行。

假设我们为家居工具商店创建了一个应用程序。用户选择一个商品,填写必要的信息,并为特定工具提交一个订单。一旦订单创建,它就会自动设置为待处理状态。为了能够监控系统中的此类订单并加入我们自己的逻辑,我们可以创建一个自定义Artisan命令,它会定时检查所有待处理的订单,如果找到任何订单,我们可以调度一个Laravel任务来进一步处理该订单(也许检查工具是否库存充足,是否可以运送到用户的地址,并发送电子邮件告知用户订单状态)。

以下是一个此类命令的示例:

namespace App\Console\Commands;

use Illuminate\Console\Command;

class MonitorOrders extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'orders:monitor';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Monitor pending tool orders and dispatch them for processing.';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        while (true) {
            // Get all the orders with the pending status
            $orders = Order::where('status', 'pending')->get();

            // If there are no orders with a pending status wait for a second then check again
            if ($orders->doesntExist()) {
                sleep(1);

                continue;
            }

            foreach ($orders as $order) {
                // Dispatch the order for further processing to a job
                ProcessOrder::dispatch($order)
                    ->onQueue('orders')
                    ->onConnection('database');
            }

            sleep(1); // Potentially take a break between runs
        }
    }
}
<h2 class="subheading">最大作业选项</h2>

如果我们想指定应处理的作业数量,类似于<a title="指定作业数量" href="https://laravel.net.cn/docs/10.x/queues#processing-a-specified-number-of-jobs" target="_blank" rel="noopener noreferrer">--max-jobs </a>标志?如果达到最大作业数量,工作进程可以退出并释放可能积累的任何内存。为了实现这一点,我们可以在命令中引入一个新选项,如下所示:

/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'orders:monitor
                        {--max-jobs=100 : The number of jobs to process before stopping}';

然后,在我们的handle方法中,我们可以设置处理的作业初始数量并从我们之前定义的选项中获取最大数量

/**
 * Execute the console command.
 */
public function handle()
{
    $jobsProcessed = 0;
    $maxJobs = $this->option('max-jobs');

    while (true) {
            // Get all the orders with the pending status
            $orders = Order::where('status', 'pending')->get();

一旦作业被发送去处理,我们将增加$jobsProcessed变量并检查工作进程是否应该退出。如果是这种情况,我们就会返回一个默认的成功退出代码(0)。在工作进程退出后,Supervisor会重启它。

foreach ($orders as $order) {
    // Dispatch the order for further processing to a job
    ProcessOrder::dispatch($order)
        ->onQueue('orders')
        ->onConnection('database');

    // Increase the number of processed jobs
    $jobsProcessed++;

    // Stop the command if the number of jobs reaches the maximum number set
    if($jobsProcessed >= $maxJobs) {
        return 0; // Success
    }
}
<h2 class="subheading">手动重启命令</h2>

当您更新命令中的代码并将其部署到服务器时,只有在Supervisor有机会实际重启工作进程之后,更改才会反映出来。为了解决这个问题,您可以在部署时重启Supervisor本身(如果更新了配置文件,重新读取配置文件)。

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart

更妙的是,我们可以创建另一个自定义命令,以此造成我们的初始化orders:monitor命令退出,再次允许Supervisor重启它。我们可以通过使用内置的Cache功能来实现这一点,类似于<a title="Queue重启" href="https://laravel.net.cn/docs/10.x/queues#queue-workers-and-deployment" target="_blank" rel="noopener noreferrer">queue:restart</a>命令那样。

以下是我们的orders:restart命令的一个示例:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;

class RestartOrders extends Command
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'orders:restart';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Restart orders monitor command after the current job.';

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        Cache::forever('orders:restart', true);
    }
}

现在,我们需要修改我们的orders:monitor命令,以检查是否触发了orders:restart。确保首先包含Cache门面。

use Illuminate\Support\Facades\Cache;

然后更新循环如下:

foreach ($orders as $order) {
    // Dispatch the order for further processing to a job
    ProcessOrder::dispatch($order)
        ->onQueue('orders')
        ->onConnection('database');

    // Increase the number of processed jobs
    $jobsProcessed++;

    // Stop the command if the number of jobs reaches the maximum number set
    if($jobsProcessed >= $maxJobs) {
        return 0; // Success
    }
    
    // Get the restart command value from the cache
    $restartCommand = Cache::get('orders:restart', false);

    // Stop the command if needed and remove the entry from the cache
    if($restartCommand) {
        Cache::forget('orders:restart');
        return 0; // Success
    }
}

这就完成了。现在您可以将orders:restart命令添加到您的部署过程中,您就不必担心代码中的任何更改没有反映出来。

正如惯例,您可以通过查看<a title="Gist示例" href="https://gist.github.com/goran-popovic/2aac3715ce25588941e23889b44c88ba" target="_blank" rel="noopener noreferrer nofollow">Gist</a>来获取源代码。

<h2 class="subheading">Supervisor配置</h2>

我们还需要做的是,在/etc/supervisor/conf.d目录(通常是该目录的位置)中添加一个名为,例如monitor-orders.conf的supervisor配置文件,该目录启动并(如果需要的话)重启我们的命令,它将不断地监控任何待处理的工具订单。

[program:monitor-orders]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/app.com/artisan orders:monitor --max-jobs=300
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log
stopwaitsecs=3600

上面的设置使用了文档中的一个示例,但您可能不需要生成8个进程(numprocs),这取决于您收到的订单数量。您的服务器上Artisan命令的路径可能完全不同;可能类似这样php /var/www/my-website.com/artisan,所以请务必检查这一点以及其他user值和worker.log文件路径。

<h2 class="subheading">结论</h2>

我们做到了。根据queue:work命令的基本示例和Laravel的文档中的说明,我们能够创建出自己定制的命令,用于监控我们Handyman Tools应用程序中的待处理订单。借助于Supervisor的帮助,这个命令将在后台自动运行,并在发生任何意外情况时重新启动。

当然,上述示例只是一个起点;你可以围绕它们构建更多的复杂逻辑;你可以引入更多条件来确定待处理订单是否应该被处理;你可以添加其他有用的选项和参数;等等。

总的来说,我希望这篇文章能帮助你更好地理解借助Supervisor运行的Artisan命令。

上次更新5个月前。

driesvints, jyllson 喜欢这篇文章

2
喜欢这篇文章吗? 让作者知道并给他们点赞!
geoligard (Goran Popović) 软件开发者,喜欢使用Laravel,在geoligard.com上写博客。

你可能还会喜欢以下文章

2024年3月11日

如何使用Larastan将你的Laravel应用从0到9进行优化

在Laravel应用执行之前找到bug是可能的,多亏了Larastan,...

阅读文章
2024年7月19日

无需特性规范API响应

我注意到大多数用于API响应的库都是使用特性实现的,...

阅读文章
2024年7月17日

通过Discord通知收集你的Laravel项目反馈

如何在Laravel项目中创建反馈模块,并在收到消息时收到Discord通知...

阅读文章

我们想感谢这些 令人惊叹的公司 对我们的支持

您在这里放置公司标志吗?

Laravel.io

用于解决问题的Laravel门户,知识共享和社区建设。

© 2024 Laravel.io - 版权所有。