如何在服务器上保留 Laravel 队列系统 运行

How to keep Laravel Queue system running on server

我最近设置了一个 Laravel 队列系统。基础知识是 cronjob 调用一个将作业添加到队列的命令,并调用第二个发送电子邮件的命令。

当我 ssh 进入我的服务器和 运行 php artisan queue:listen 时,系统工作,但如果我关闭我的终端,侦听器将关闭并且作业堆积并等待排队,直到我 ssh 返回并 运行 再次收听。

让我的队列系统 运行ning 在后台运行而不需要通过 ssh 保持连接打开的最佳方法是什么?

我尝试了 运行ning php artisan queue:work --daemon,它完成了队列中的作业,但是当我关闭终端时,它关闭了连接和后台进程。

如果您在一个屏幕内开始收听怎么办?看这里:http://aperiodic.net/screen/quick_reference 然后即使您退出,屏幕仍然处于活动状态并且 运行。不确定为什么守护进程不起作用。

运行

nohup php artisan queue:work --daemon &

将在您注销时阻止命令退出。

尾随符号 (&) 会导致进程在后台启动,因此您可以继续使用 shell 而不必等到脚本完成。

参见 nohup

nohup - run a command immune to hangups, with output to a non-tty

这会将信息输出到您 运行 命令所在目录中名为 nohup.out 的文件中。如果您对输出不感兴趣,您可以将 stdout 和 stderr 重定向到 /dev/null,或者类似地,您可以将其输出到您的正常 laravel 日志中。例如

nohup php artisan queue:work --daemon > /dev/null 2>&1 &

nohup php artisan queue:work --daemon > app/storage/logs/laravel.log &

但是您还应该使用 Supervisord 之类的东西来确保服务保持 运行ning 并在 crashes/failures 后重新启动。

命令

nohup php artisan queue:work --daemon &

是正确的,它将允许进程在关闭 SSH 连接后继续;但是,这只是短期修复。一旦您的服务器重新启动或任何问题导致进程停止,您将需要返回并再次 运行 命令。当这种情况发生时,你永远不会知道。它可能发生在星期五晚上,因此最好实施长期解决方案。

我最终切换到了 Supervisord,这可以像

一样简单地安装在 Ubuntu 上
sudo apt-get install supervisor 

对于 AWS-AMI 或 RedHat 用户,您可以遵循我在这个问题中概述的一组说明:

你应该使用 linux supervisor

安装很简单 Ubuntu 我可以使用以下命令安装它:

apt-get install supervisor

管理员配置文件位于 /etc/supervisor/conf.d 目录。

[program:email-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/laravel-example/artisan queue:work redis --queue=emailqueue --sleep=3 --tries=3
autostart=true
autorestart=true
user=forge
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/laravel-example//storage/logs/supervisord.log

您应该为每个进程创建一个新的进程配置文件。使用此配置,侦听器将重试每个作业 3 次。如果失败或系统重启,Supervisor 也会重启监听器。

来自https://gist.github.com/ivanvermeyen/b72061c5d70c61e86875

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Ensure that the queue listener is running.';

    /**
     * Create a new command instance.
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if ( ! $this->isQueueListenerRunning()) {
            $this->comment('Queue listener is being started.');
            $pid = $this->startQueueListener();
            $this->saveQueueListenerPID($pid);
        }

        $this->comment('Queue listener is running.');
    }

    /**
     * Check if the queue listener is running.
     *
     * @return bool
     */
    private function isQueueListenerRunning()
    {
        if ( ! $pid = $this->getLastQueueListenerPID()) {
            return false;
        }

        $process = exec("ps -p $pid -opid=,cmd=");
        //$processIsQueueListener = str_contains($process, 'queue:listen'); // 5.1
        $processIsQueueListener = ! empty($process); // 5.6 - see comments

        return $processIsQueueListener;
    }

    /**
     * Get any existing queue listener PID.
     *
     * @return bool|string
     */
    private function getLastQueueListenerPID()
    {
        if ( ! file_exists(__DIR__ . '/queue.pid')) {
            return false;
        }

        return file_get_contents(__DIR__ . '/queue.pid');
    }

    /**
     * Save the queue listener PID to a file.
     *
     * @param $pid
     *
     * @return void
     */
    private function saveQueueListenerPID($pid)
    {
        file_put_contents(__DIR__ . '/queue.pid', $pid);
    }

    /**
     * Start the queue listener.
     *
     * @return int
     */
    private function startQueueListener()
    {
        //$command = 'php-cli ' . base_path() . '/artisan queue:listen --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.1
        $command = 'php-cli ' . base_path() . '/artisan queue:work --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.6 - see comments
        $pid = exec($command);

        return $pid;
    }
}

您可以使用 monit 工具。它非常小,对任何类型的流程管理和监控都很有用。

this link 下载二进制包后,您可以将其解压缩到系统上的一个文件夹中,然后从包中复制两个文件到您的系统中进行安装:

cd /path/to/monit/folder
cp ./bin/monit /usr/sbin/monit
cp ./conf/monitrc /etc/monitrc  

现在根据您的需要编辑 /etc/monitrcreference doc). then create a init control file 以在启动时启用监控。现在像这样启动监控:

initctl reload-configuration
start monit

对于那些已经在其生产环境中 运行 宁 NodeJS 的人。我使用 PM2 来管理应用程序进程。

# install
npm install -g pm2

# in project dir with your CI or dev setup tool 
# --name gives task a name so that you can later manage it
# -- delimits arguments that get passed to the script
pm2 start artisan --interpreter php --name queue-worker -- queue:work --daemon

我在开发和设置 NodeJS 时使用 Vagrant,这个过程只使用内联 vagrant 脚本。

当您在开发中使用 PM2 时,您可以使用众多观察者之一来管理重启。取零时只需 运行 pm2 restart queue-worker 即可。在生产中我不推荐这种方法,而是选择可以遵循此过程的构建工具。

# 1. stop pm task to ensure that no unexpected behaviour occurs during build
pm2 stop queue-worker
# 2. do your build tasks
...
# 3. restart queue so that it loads the new code
pm2 restart queue-worker

使用 pm2

我有 JS 脚本 运行 pm2(高级,Node.js 的生产流程经理)这是我 运行 唯一的一个。但是现在我又多了一个过程来保持 运行ning。

我用一个命令创建了 process.yml 到 运行。 检查第一个会 运行 php artisan queue: listen

# process.yml at /var/www/ which is root dir of the project
apps:
  # Run php artisan queue:listen to execute queue job
  - script    : 'artisan'
    name      : 'artisan-queue-listen'
    cwd       : '/var/www/'
    args      : 'queue:listen' # or queue:work
    interpreter : 'php'

  # same way add any other script if any.

现在运行:

> sudo pm2 start process.yml

Check more options and feature of pm2

由于这是一个 Laravel 特定的问题,我想我会建议一个 Lravel 特定的答案。由于您已经在此服务器上使用 cronjobs,我建议您将 shell 命令设置为循环 cronjob 以始终验证工作人员是否正在 运行ning。您可以通过服务器上的 cron 在本地将 shell 命令设置为 运行,或者您可以使用 Laravel 控制台内核来管理命令并添加逻辑,例如检查您是否已经有一个 worker 运行ning,如果没有,继续并重新启动它。

根据您需要 运行 您的命令的频率,您可以每周执行一次,甚至每分钟一次。这将使您能够确保您的工作人员持续 运行ning,而无需向您的服务器(例如 Supervisor)增加任何开销。如果您信任 supervisor 等 3rd 方包,则可以授予权限,但如果您可以避免依赖它,则可能需要考虑这种方法。

使用它来做你想做的事情的一个例子是有一个每小时 运行s 的 cronjob。它将从自定义 Laravel 控制台命令中按顺序执行以下命令:

\Artisan::call('queue:restart');

\Artisan::call('queue:work --daemon');

请注意,这适用于 Laravel 的旧版本(最高 5.3),但我尚未在较新版本上进行测试。

我只是使用了 php artisan queue:work --tries=3 &,它使进程 运行ning 保持在后台。 但它有时会停止。 我不知道为什么会这样

编辑

我使用 supervisor 解决了这个问题。 放置一个主管脚本 运行s 这个 php 脚本,每次服务器 运行s

时都会 运行

最好的方法是 PM2(Node.js 的高级生产流程管理器),您可以监控队列并查看他们的日志。

在您的项目目录中使用以下命令,运行 queue worker :

pm2 start artisan --name laravel-worker --interpreter php -- queue:work --daemon

1)sudo apt install supervisor

sudo apt-get install supervisor

2)cd /etc/supervisor/conf.d 3) 在

中创建新文件
sudo vim queue-worker.conf

文件内容

[program:email-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/laravelproject/artisan queue:work
autostart=true
autorestart=true
user=root
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/html/laravelproject/storage/logs/supervisord.log

4)sudo supervisorctl reread

当运行这个命令得到输出queue-worker:available

5)sudo supervisorctl update

当运行这个命令得到输出队列工作者:添加进程组

其他命令

1)sudo supervisorctl reload

当运行这个命令得到输出 Restarted supervisord

2)sudo service supervisor restart

Installing Supervisor

sudo apt-get install supervisor

Configuring Supervisor

第 1 步:转到 /etc/supervisor/conf.d 目录

cd /etc/supervisor/conf.d

第 2 步:创建一个将监听队列 laravel-worker.conf 的工作文件

sudo nano laravel-worker.conf

*注意: 现在假设您的 laravel 应用位于 /var/www/html 目录

project folder is : /var/www/html/LaravelApp

第 3 步:将下面的代码粘贴到 laravel-worker.conf 并保存文件

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/LaravelApp/artisan queue:listen redis --queue=default --sleep=3 --tries=3 
autostart=true
autorestart=true
user=root
numprocs=8
redirect_stderr=true
stdout_logfile= /var/www/html/LaravelApp/storage/logs/worker.log

*注:这里假设你使用redis进行队列连接

在 .env 文件中 QUEUE_CONNECTION=redis

command=php /var/www/html/LaravelApp/artisan queue:listen redis

如果您使用其他连接,则一般语法为:

command= php [project_folder_path]/artisan queue:listen [connection_name]

[connection_name] 可以是 sync , database , beanstalkd , sqs , redis

第 4 步:创建一个工作文件 laravel-schedule.conf,它将 运行 artisan schedule:run 命令在 每 1 分钟(60 秒)(*您可以根据需要更改)

[program:laravel-schedule]
process_name=%(program_name)s_%(process_num)02d
command=/bin/bash -c 'while true; do date && php /var/www/html/LaravelApp/artisan schedule:run; sleep 60; done'
autostart=true
autorestart=true
numprocs=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

第 5 步:启动主管:运行 下面的命令

sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start all

*Note : Whenever you make changes in any of configuration .conf files , run above commands of Step 5

额外有用的信息:

  • 停止所有 supervisorctl 进程:sudo supervisorctl stop all
  • 重新启动所有 supervisorctl 进程:sudo supervisorctl restart all

有用的链接:

https://laravel.com/docs/5.8/queues#running-the-queue-worker

http://supervisord.org/index.html

对于 CentOS7

yum install supervisor

然后在/etc/supervisord.d/filename.ini中创建一个文件 有内容

[program:laravel-worker]
command=/usr/bin/php /home/appuser/public_html/artisan queue:listen
process_name=%(program_name)s_%(process_num)02d
numprocs=5
priority=999
autostart=true
autorestart=true
startsecs=1
startretries=3
user=appuser
redirect_stderr=true
stdout_logfile=/path/logpath/artisan.log

然后使用

启动supervisord服务
systemctl restart supervisord

使用

在启动时为运行启用supervisord服务
systemctl enable supervisord

检查服务是否运行正在使用

ps aux | grep artisan

如果设置正确,您应该会看到进程 运行ning。类似于下面的输出。

[root@server ~]# ps aux | grep artisan
appuser 17444  0.1  0.8 378656 31068 ?        S    12:43   0:05 /usr/bin/php /home/appuser/public_html/artisan queue:listen

您还可以使用 Docker 个容器。

结帐:

对于使用 systemd 作为初始服务的系统,您可以使用以下服务,使其适应您的项目(在 /etc/systemd/system/queue-handler.service 上创建):

[Unit]
Description = Queue Handler - Project
After = network-online.target, mysql.service

[Service]
User = www-data
Type = simple
WorkingDirectory=/var/www/project
ExecStart = /usr/bin/php /var/www/project/artisan queue:work --tries=3
Restart = on-failure
RestartSec=5s
RestartPreventExitStatus = 255

[Install]
WantedBy = multi-user.target

重新加载配置并在启动时启用它:

$ systemctl enable queue-handler.service
$ systemctl daemon-reload

我在没有任何服务监视器或第三方软件的情况下获得了结果。该解决方案工作正常,但我不确定它是否是最好的方法。

解决方案

只需 运行 在您的函数中按以下方式执行 cli 命令。

use Illuminate\Console\Command;

public function callQueue()
{
        $restart = 'php-cli ' . base_path() . '/artisan queue:restart > /dev/null & echo $!'; 
        $work = 'php-cli ' . base_path() . '/artisan queue:work --timeout=0 --sleep=5 --tries=3 > /dev/null & echo $!';
        exec($restart);
        exec($work);
}

$job = (new jobName())->delay(Carbon::now()->addSeconds(5));
dispatch($job);

原因

我使用这两个命令的原因是因为根据 中的评论,与 $restart 关联的命令防止出现任何内存问题 并且与 $work 关联的命令确保该命令在作业之前成功执行。

您可以运行命令行:

php artisan queue:listen --queue=queue_name --tries=1 --memory=128 --timeout=300 >>  storage/logs/queue_log.log &

检查进程运行宁:

ps aux | grep php

在 Ubuntu

上使用 AWS SQS 连接

正在安装主管

sudo apt-get install supervisor

配置主管

第 1 步:转到 /etc/supervisor/conf.d 目录

cd /etc/supervisor/conf.d

第 2 步:创建一个工作文件 laravel-worker.conf 来监听队列

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=/opt/bitnami/php/bin/php /opt/bitnami/nginx/html/website/queue:work sqs
autostart=true
autorestart=true
user=root
numprocs=2
redirect_stderr=true
startsecs=0
stdout_logfile=/opt/bitnami/nginx/html/website/storage/logs/supervisord.log

4)sudo supervisorctl reread

当运行这个命令得到输出queue-worker:available

5)sudo supervisorctl update

当运行这个命令得到输出queue-worker:added process group

其他命令

1)sudo supervisorctl reload

当运行这个命令得到输出 Restarted supervisord

2)sudo service supervisor restart

3)sudo supervisorctl status

查看进程状态

根据您的环境及其配置能力,为这只猫设置皮肤的方法不止一种。有些在共享主机上,有些有 whm 访问权限...

更直接和可维护的方法如下;

在您的内核文件中将 $schedule->command('queue:work')->everyFiveMinutes()->withoutOverlapping(); 添加到您的调度方法。

这将 运行 每五分钟执行一次队列工作命令。

/**
 * Define the application's command schedule.
 *
 * @param Schedule $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    $schedule->command('queue:work')->everyFiveMinutes()->withoutOverlapping();
}

请参阅 laravel 文档 - https://laravel.com/docs/9.x/scheduling#defining-schedules