嗨,我是Valerio,来自意大利的软件工程师。
该指南适用于所有具有与实际用户在线应用程序的PHP开发人员,但他们需要更深入地了解如何使用Laravel队列在其系统中引入(或大幅度提高)可伸缩性。
我第一次阅读有关Laravel的信息是在2013年末,即框架5.x版的开始。 我还不是从事重大项目的开发人员,现代框架(尤其是在Laravel中)对我来说最神秘的方面之一就是“队列”。
阅读文档时,我猜测了潜力,但没有真正的开发经验,它只是一个理论。
今天,我是Inspector.dev的创建者,它是一个实时监控仪表板 ,每小时执行数千个作业,因此我对这种体系结构的了解比过去更好。
![](https://i-blog.csdnimg.cn/blog_migrate/4d111370c68d80d6017f9769ef90fcde.png)
在本文中,我将向您展示如何发现队列和作业,以及哪些配置可以帮助我实时处理大量数据,同时又使服务器资源保持成本低廉。
简要介绍
当PHP应用程序收到传入的http请求时,将逐步逐步执行我们的代码,直到请求的执行结束,并将响应返回给客户端(例如,用户的浏览器)为止。
同步行为确实是直观,可预测且易于理解的。 我向端点发起一个http请求,应用程序从数据库中检索数据,将其转换为适当的格式,执行一些其他任务,然后将其发送回去。 它是线性的。
队列和作业引入了异步行为,破坏了这种线性流。 这就是为什么我认为这些功能在一开始对我来说有点奇怪。
但是有时,一项耗时的任务涉及到由于传入的http请求而完成一个执行周期,例如,向项目的所有团队成员发送电子邮件通知。
这可能意味着发送六到十封电子邮件,可能需要四到五秒钟才能完成。 因此,每次用户单击该按钮时,他们需要等待五秒钟才能继续使用该应用程序。
应用程序增长越多,此问题就越严重。
什么是工作?
Job是实现“ 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;
/**
* Create a new job instance.
*
* @param array $Data
*/
public function __construct ($url)
{
$this ->url = $url;
}
/**
* Execute what you want.
*
* @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());
// Defer NotifyMembers, TagUserActive, NotifyToProveSource
// passing the information needed to do their job
Notification::queue( new NotifyMembers($project->owners));
$this ->dispatch( new TagUserAsActive($project->owners));
$this ->dispatch( new NotifyToProveSource($project->owners));
return $project;
}
}
我不需要等到所有这些过程都完成后再返回响应。 相反,我只会等待将它们发布到队列中所需的时间。 这可能意味着10秒和10毫秒之间的差异!!!
将这些作业发布到队列中后,谁来执行这些作业?
这是经典的“发布者/消费者”架构。 我们刚刚将作业从控制器发布到队列中,所以现在我们将了解如何使用队列,最后执行作业。
![](https://i-blog.csdnimg.cn/blog_migrate/45d00e6de550d771b53ecd9bb4f0d555.png)
要消耗队列,我们需要运行最流行的artisan命令之一:
php artisan queue :work
如Laravel文档中所述 :
Laravel包括一个队列工作器,它将在新任务被推送到队列时对其进行处理。
大! Laravel提供了一个准备使用的界面来将作业放入队列,并提供了一个准备使用的命令来将作业从队列中拉出并在后台执行其代码。
主管的角色
一开始这是另一件“奇怪的事情”。 我认为发现新事物很正常。 我也经历了这个学习阶段,因此我写这些文章来帮助我组织技能,同时我可以帮助其他开发人员扩展他们的知识。
如果作业在触发异常时失败,queue:work命令将停止其工作。
要使queue:work进程永久运行(消耗队列),应使用诸如Supervisor之类的进程监视器来确保即使作业引发异常, queue:work
命令也不会停止运行。
管理员在命令失败后重新启动命令,从下一个作业重新开始,放弃失败的命令。
作业将在服务器的后台执行,而不再取决于HTTP请求。 这引入了一些在实现作业代码时必须考虑的更改。
在我看来,这是最重要的:
我如何知道作业代码是否失败?
在后台运行,您无法立即查看您的作业是否产生错误。
您将不再像从浏览器中运行http请求那样立即获得反馈。 如果工作失败,他会默默地做,而不会引起任何注意。
考虑集成像 Inspector 这样的实时监视工具 ,以发现每个缺点。
您没有http请求
http请求不见了 。 您的代码将从cli执行。
如果您需要请求参数来完成任务,则需要在作业的构造函数中传递它们,以便稍后在执行期间使用:
<?php
// A job class example
class TagUserJob implements ShouldQueue
{
public $data;
public function __construct (array $data)
{
$this ->data = $data;
}
}
// Put the job in the queue from your controller
$this ->dispatch( new TagUserJob($request->all()));
您不知道谁是登录用户
会话不见了。 以同样的方式,您将不知道登录用户的身份,因此,如果您需要用户信息来完成任务,则需要将用户对象传递给作业的构造函数:
<?php
// A job class example
class TagUserJob implements ShouldQueue
{
public $user;
public function __construct (User $user)
{
$this ->user= $user;
}
}
// Put the job in the queue from your controller
$this ->dispatch( new TagUserJob($request->user()));
了解如何缩放
不幸的是,在许多情况下还不够。 使用单个队列和使用者,它可能很快变得无用。
队列是FIFO缓冲器(F IRST I N 开始步骤ØUT)。 如果您计划许多任务(也属于不同类型),则它们需要等待其他人执行计划的任务才能完成
有两种扩展方式:
多个使用者排队
![](https://i-blog.csdnimg.cn/blog_migrate/d3f250665d7b45623e71c6f06a498234.png)
这样,一次可以从队列中拉出五个作业,从而加快了队列的消耗。
单用途队列
您还可以为要启动的每个作业“类型”创建特定的队列,并为每个队列使用专用的使用者。
![](https://i-blog.csdnimg.cn/blog_migrate/0cd43abc3d32a8fc796dfed4efbedde9.png)
这样,每个队列将被独立使用,而不必等待其他类型的作业的执行。
迈向地平线
Laravel Horizon 是一个队列管理器 ,可让您完全控制要设置的队列数量以及组织使用者的能力,从而使开发人员可以将这两种策略放在一起,并实施适合您的可伸缩性需求的策略。
一切始于运行php artisan Horizon,而不是php artisan queue:work。 此命令扫描您的horizon.php
配置文件并根据配置启动许多队列工作器:
<?php
'production' => [
'supervisor-1' => [
'connection' => "redis" ,
'queue' => [ 'adveritisement' , 'logs' , 'phones' ],
'processes' => 9 ,
'tries' => 3 ,
'balance' => 'simple' , // could be simple, auto, or null
]
]
在上面的示例中,Horizon将启动三个队列,并分配三个进程来使用每个队列。
如Laravel文档中所述,Horizon的代码驱动方法允许我的配置保留在源代码控制中,以便团队进行协作。 这也是使用CI工具的完美解决方案。
要详细了解配置选项的含义,请考虑阅读以下精美文章: https : //medium.com/@zechdc/laravel-horizon-number-of-workers-and-job-execution-order-21b9dbec72d7
我自己的配置
<?php
'production' => [
'supervisor-1' => [
'connection' => 'redis' ,
'queue' => [ 'default' , 'ingest' , 'notifications' ],
'balance' => 'auto' ,
'processes' => 15 ,
'tries' => 3 ,
],
]
检查器主要使用三个队列:
- 摄取是用于流程分析来自外部应用程序的数据;
- 通知用于在数据摄取过程中检测到错误时立即安排通知;
- default用于其他我不想干扰摄取和通知过程的任务。
使用balance = auto时, Horizon知道要激活的最大进程数为15,它将根据队列负载动态分配。
如果队列为空,则Horizon会为每个队列保持一个进程处于活动状态,如果计划了作业,则使使用者可以立即处理该队列。
最后的笔记
并发后台执行可能导致许多其他不可预测的错误,例如MySQL“超出了锁等待超时”和许多其他设计问题。 在此处阅读更多信息: https : //www.inspector.dev/resolve-mysql-lock-wait-timeout-dealing-with-laravel-queues-and-jobs/
我希望本文能帮助您更加自信地使用队列和作业。 如果您想了解更多有关我们的信息,请访问我们的网站https://www.inspector.dev
先前发布在https://www.inspector.dev/what-worked-for-me-using-laravel-queues-from-the-basics-to-horizon/
From: https://hackernoon.com/make-your-product-ready-to-scale-using-laravel-queues-dqa237if