使我们的产品可以通过Laravel队列进行扩展

本文的翻译是专门为“ Laravel”课程的学生准备的




嗨,我是来自意大利的软件工程师Valerio。

本指南适用于已经有实际用户在线应用程序,但对如何使用Laravel队列在系统中实现(或显着提高)可伸缩性没有更深入了解的所有PHP开发人员。我于2013年底在框架第5版开始时了解了Laravel。那时我还不是从事严肃项目的开发人员,而现代框架的各个方面之一,尤其是在Laravel中,对我来说似乎最不可理解的就是队列。

阅读文档时,我猜到了潜力,但没有真正的开发经验,所有这些都仅停留在我脑海中。
今天,我的创造者Inspector.dev- 实时仪表盘,它执行数千个任务每隔一小时,我这个架构比以前更好的了解。



在本文中,我将告诉您如何发现队列和任务以及哪些配置可以帮助我实时处理大量数据,同时节省服务器资源。

介绍


当PHP应用程序收到传入的http请求时,我们的代码将逐步逐步执行,直到请求完成,并将响应返回给客户端(例如,用户的浏览器)为止。这种同步行为是真正直观,可预测且易于理解的。我向端点发送一个http请求,应用程序从数据库中提取数据,将其转换为适当的格式,执行一些其他任务,然后将它们发送回去。一切都是线性发生的。

任务和队列引入了违反此行流的异步行为。这就是为什么这些功能乍一看对我来说有点奇怪的原因。
但是有时传入的http请求可能触发一系列耗时的任务,例如,向所有团队成员发送电子邮件通知。这可能意味着发送六到十封电子邮件,这可能需要四到五秒钟。因此,每次用户单击相应的按钮时,他需要等待五秒钟才能继续使用该应用程序。

应用程序增长得越多,这个问题就越严重。

什么是任务?


作业是一个实现handle方法的类,该方法包含我们要异步执行的逻辑。

<?php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Bus\Queueable;


class CallExternalAPI implements ShouldQueue
{
    use Dispatchable,
        InteractsWithQueue,
        Queueable;
        
    /**
     * @var string
     */
    protected $url;

    /**
     *    .
     *
     * @param array $Data
     */
    public function __construct($url)
    {
        $this->url = $url;
    }
    

    /**
     *  ,   .
     *
     * @return void
     * @throws \Throwable
     */
    public function handle()
    {
        file_get_contents($this->url);
    }
}

如上所述,在Job中编写一段代码的主要原因是完成耗时的任务而不必等待用户完成它。

我们所说的“繁重的任务”是什么意思?


这是一个自然的问题。发送电子邮件是讨论队列的文章中最常用的示例,但是我想向您介绍我需要做的事情的真实经验。

作为产品所有者,对我来说非常重要的是将用户旅行信息与我们的营销和客户支持工具同步。因此,基于用户行为,我们会通过API(或外部http调用)在各种外部软件中更新用户信息,以实现服务和营销目的。
在我的应用程序中,最常用的端点之一可以发送10封电子邮件,并在完成之前对外部服务进行3次http调用。没有用户会等待那么多时间-最有可能的是,他们都只是停止使用我的应用程序。

多亏了队列,我可以将所有这些任务封装在分配的类中,将执行其工作所需的信息传递给构造函数,并安排它们在后台稍后完成,以便我的控制器可以立即返回答案。

<?php

class ProjectController 
{
    public function store(Request $request)
    {
        $project = Project::create($request->all());
        
        //  NotifyMembers, TagUserActive, NotifyToProveSource 
        //   ,     
        Notification::queue(new NotifyMembers($project->owners));
        $this->dispatch(new TagUserAsActive($project->owners));
        $this->dispatch(new NotifyToProveSource($project->owners));
        
        return $project;
    }
}

在返回答案之前,我不需要等待所有这些过程的完成。相反,等待时间等于将它们发布到队列中所需的时间。这意味着10秒和10毫秒之间的差!

将这些任务发送到队列后,谁来执行这些任务?


这是经典的发布者/消费者体系结构我们已经从控制器将任务发布到队列中,现在我们将了解如何使用队列,最后执行任务。



要使用队列,我们​​需要运行最受欢迎的工匠命令之一:

php artisan queue:work

如文档所述:
Laravel包括一个队列工作人员,该工作人员在新任务排队时对其进行处理。

大!Laravel提供了一个用于排队任务的现成接口和一个现成的命令,用于从队列中检索任务并在后台执行其代码。

主管角色


刚开始时,这是另一件事。我认为发现新事物很正常。经过这一阶段的培训后,我写了这些文章来帮助自己组织技能,同时帮助其他开发人员扩展知识。

如果在引发异常时任务失败,团队将queue:work停止工作。

为了使进程queue:work连续运行(消耗队列),必须使用进程监视器(例如Supervisor)来确保queue:work即使任务引发异常命令也不会停止工作,Supervisor 在崩溃后重新启动命令,然后启动再次从下一个任务开始,抛开抛出异常。

任务将在服务器的后台运行,不再依赖于HTTP请求。这引入了一些在实现任务代码时必须考虑的更改。

以下是我要注意的最重要的事项:

如何确定任务代码不起作用?


在后台运行时,您无法立即注意到您的任务正在引起错误。
您将不再立即获得反馈,例如从浏览器发出http请求时。如果任务失败,它将以静默方式执行,没有人会注意到。

考虑集成诸如Inspector之类的实时监视工具以发现每个缺陷。

您没有http请求


没有更多的HTTP请求。您的代码将从cli执行。

如果需要查询参数来完成任务,则需要将它们传递给任务构造函数以供以后在运行时使用:

<?php

//   

class TagUserJob implements ShouldQueue
{
    public $data;
    
    public function __construct(array $data)
    {
        $this->data = $data;
    }
}

//       

$this->dispatch(new TagUserJob($request->all()));

您不知道谁登录


不再有会议同样,您将不知道登录用户的身份,因此,如果您需要有关用户的信息以完成任务,则需要将用户对象传递给任务构造函数:

<?php

//   
class TagUserJob implements ShouldQueue
{
    public $user;
    
    public function __construct(User $user)
    {
        $this->user= $user;
    }
}

//       
$this->dispatch(new TagUserJob($request->user()));

了解如何缩放


不幸的是,在许多情况下,这还不够。使用一条线路和一个消费者可能很快就会变得毫无用处。

队列是FIFO缓冲区(“先进先出”)。如果您计划了很多任务,甚至可能是不同类型的任务,他们都需要等待其他人完成任务才能完成。

有两种扩展方式:

每个队列几个使用者,



因此一次将五个任务从队列中删除,从而加快了队列处理速度。

专用队列

您还可以为要启动的每种任务创建特定的队列,并为每个队列使用专用的消费者。



因此,每个队列将被独立处理,而无需等待其他类型的任务完成。

地平线


Laravel Horizo​​n是一个队列管理器,可让您完全控制要设置的队列数量以及组织使用者的能力,从而使开发人员可以将这两种策略结合起来并实施适合您的可伸缩性需求的策略。

启动是通过php artisan地平线而不是php artisan队列:work进行的。此命令将扫描配置文件,horizon.php并根据配置启动一系列队列工作器:

<?php

'production' => [
    'supervisor-1' => [
        'connection' => "redis",
        'queue' => ['adveritisement', 'logs', 'phones'],
        'processes' => 9,
        'tries' => 3,
        'balance' => 'simple', //   simple, auto  null

    ]
]

在上面的示例中,Horizo​​n将启动三个队列,并分配三个进程来处理每个队列。如Laravel文档中所述,Horizo​​n的代码驱动方法允许我的配置保留在版本控制系统中,我的团队可以在该版本中进行协作。这是使用CI工具的完美解决方案。

要详细了解配置参数的含义,请阅读这篇精彩的文章

我自己的配置



<?php

'production' => [
    'supervisor-1' => [
        'connection' => 'redis',
        'queue' => ['default', 'ingest', 'notifications'],
        'balance' => 'auto',
        'processes' => 15,
        'tries' => 3,
    ],
]

检查器主要使用三个队列:

  • 摄取用于分析来自外部应用程序的数据的流程;
  • 通知用于在接收数据时检测到错误时立即安排通知;
  • default用于其他我不想与摄取通知流程混合的任务

通过使用balance=autoHorizo​​n,他了解激活的最大进程数是15,它将根据队列的负载动态分配。

如果队列为空,则Horizo​​n为每个队列支持一个活动进程,如果计划了任务,则使用者可以立即处理该队列。

结论性意见


并发后台执行可能导致许多其他不可预测的错误,例如MySQL“锁定超时”和许多其他设计问题。在这里阅读更多

我希望本文能帮助您更加自信地使用队列和任务。如果您想了解更多有关我们的信息,请访问我们的网站:www.inspector.dev

先前在这里发布:www.inspector.dev/what-worked-for-me-using-laravel-queues-from-the-basics-to -地平线



上课程并享受折扣。



All Articles