我正在使用Laravel 5.5,并试图设置一些快速队列处理。我遇到了一个又一个路障。
该网站是一个雇主/雇员匹配服务。因此,当雇主发布一个职位时,它需要遍历我们系统中的所有员工,并计算一些变量,以确定他们与工作的匹配程度。我们已经弄清楚了这一切,但当系统中有数千名员工时,一次处理一个需要很长时间。所以,我准备写几个表格。第一个是一个简单的表,它定义了位置ID和状态。第二个表列出了所有员工ID、职位ID和正在处理的员工的状态。这只需要几秒钟的写入时间,然后允许用户在应用程序中继续前进。
然后,我有另一个服务器设置,每分钟运行一个cron,检查第一个表中的新条目。当被发现时,它会将其标记为已启动,然后抓住所有员工,遍历每个员工,并在Laravel启动排队的工作。我定义的作业确实正确地提交到队列,并且运行queue:work
实际上确实正确地处理了作业。这一切都经过了测试。
然而,我遇到的问题是,我已经为队列尝试了数据库(MySQL)、Redis和SQS,但它们都很慢。我使用同一台服务器尝试操作queue:work
(使用Supervisor并尝试运行多达300个进程),但随后创建了3个不运行cron但只运行Supervisor的克隆(每个克隆100个进程)并在第一台服务器上杀死了Supervisor。有了数据库,它可以处理,尽管运行一万个排队的作业需要几个小时,但有了SQS和Redis,我会遇到很多故障。脚本花费的时间太长了。我检查了运行工作程序的克隆上的CPU,它们几乎没有达到40%,所以我没有对服务器过度征税。
我刚刚读到关于地平线的文章,我不确定它是否会对这种情况有所帮助。我一直在努力寻找如何使用Laravel正确设置队列处理系统的信息,但遇到的问题总是多于答案。
有人熟悉这些东西吗?有人对如何正确设置这些东西有什么建议吗?以便它非常快速且无故障(假设我的代码没有错误)?
更新:根据其他帖子的建议,我想我会分享更多细节:
- 我使用Forge作为带有2G RAM的AWS EC2服务器的设置工具
-
三个克隆中的每一个都具有以下工作程序配置:
command=php /home/forge/default/artisan queue:work sqs --sleep=10 --daemon --quiet --timeout=30 --tries=3 process_name=%(program_name)s_%(process_num)02d autostart=true autorestart=true stopasgroup=true killasgroup=true user=forge numprocs=100 stdout_logfile=/home/forge/.forge/worker-149257.log
-
该数据库位于亚马逊RDS上。
我很好奇Laravel缓存是否能与队列系统一起工作。排队脚本中有一些元素对每次运行都是通用的,所以如果我从一开始就将数据排队,可能会节省一些时间。但我不相信这会是一个巨大的进步。
如果我们忽略每个作业处理的实际逻辑,只考虑运行作业的开销,Laravel的排队系统可以在问题中描述的环境中轻松地每小时处理10000个作业,如果不是几倍的话,尤其是Redis后端。
对于典型的队列设置,每个框100个队列工作进程似乎非常高。除非这些作业在等待状态下花费大量时间,例如通过网络向web服务发出请求并仅用几毫秒处理响应的作业,否则大量并发的进程实际上会降低性能。每个处理器核心运行一个以上的工作程序不会给我们带来太多好处。额外的工作人员会产生开销,因为操作系统必须在所有竞争进程之间分配和调度计算时间。
我检查了运行工作程序的克隆上的CPU,它们几乎没有达到40%,所以我没有对服务器过度征税。
在不了解项目的情况下,我可以建议这些工作可能确实花了一些时间等待。你可能需要调整工人的数量,以找到空闲时间和过度拥挤之间的最佳点。
对于数据库,它可以处理,尽管运行10k个排队的作业需要几个小时,但对于sqs和redis,我会遇到很多故障。
如果您将错误消息和任何其他相关信息添加到问题中,我将尝试更新此答案。
我很好奇Laravel缓存是否能与队列系统一起工作。排队脚本中有一些元素对每次运行都是通用的,所以如果我从一开始就将数据排队,可能会节省一些时间。
在执行队列中的作业时,我们当然可以使用缓存API。我们看到的任何性能改进都取决于我们可以存储在缓存中的每个作业的数据复制成本。我不能确定缓存将如何节省多少时间,因为我不熟悉这个项目,但你可以在作业中对代码部分进行分析,以找到昂贵的操作。
或者,我们可以将可重用数据缓存在内存中。当我们使用artisan queue:work
初始化队列工作程序时,Laravel启动一个PHP进程,并为该工作程序执行的所有作业引导应用程序一次。这与典型的PHP web应用程序的应用程序生命周期不同,其中应用程序在每个请求时都会重新启动,并在每个请求结束时处理状态。因为每个作业都在同一个进程中执行,所以我们可以创建一个对象,将共享作业数据缓存在进程内存中,也许可以将一个单例绑定到IoC容器中,因为我们避免了从缓存后端获取数据所需的开销,所以作业读取IoC容器的速度甚至比Redis缓存更快。
当然,这也意味着我们需要确保我们的作业不会泄漏内存,即使我们没有如上所述缓存数据。
我刚刚读到关于地平线的文章,我不确定它是否会对这种情况有所帮助。
Horizon提供监控服务,可能有助于跟踪此设置的问题。如果应用程序使用其他队列,Horizon可以在空闲时在这些队列之间分配工作,这也可能会稍微提高效率,但这个问题似乎并不表明情况确实如此。
三个克隆中的每一个都有以下辅助配置:
command=php /home/forge/default/artisan queue:work sqs --sleep=10 --daemon --quiet --timeout=30 --tries=3
(旁注:对于Laravel 5.3及更高版本,不推荐使用--daemon
选项,默认情况下queue:work
命令以守护进程模式运行。)