Laravel cron/queue/workers 在多个服务器上设置

Laravel cron/queue/workers setup on multiple servers

我有多台服务器共享一个数据库 - 每台服务器上每 5 分钟都会触发一个 cron 作业,检查文本消息日志条目是否不存在,创建文本消息日志条目并发送一条文本消息.我以为永远不会出现多次发送短信的情况,因为一个服务器应该是第一个。

嗯 - 我错了,那个场景确实发生了:

我已更改此行为以引入队列,这应该可以缓解该问题。虽然 crons 仍然会触发,但多个作业将排队,工作人员应该在不同的时间选择给定的作业,从而防止消息发送两次。虽然它最终也可能是:

等等,或者 A 和 B 还不如在同一时间接同一份工作。

我猜,解决方案是 运行 一台工作服务器。但是后来我遇到了来自多个服务器的作业多次排队的情况,当我们以第一种情况结束时,我无法检查它们是否已经排队。

我不知道如何在这里继续 - 虽然多台服务器,一台工作服务器设置可以工作,但我不想以同一作业(来自不同服务器)的实例多次结束排队。

也许解决方案是拥有一台 cron/queue/worker 服务器,但我没有 Laravel/multiserver 环境的经验来设置它。

对我来说另一个问题是——如何测试这个?我想我不能在本地测试它,除非有一种方法可以旋转彼此同步的 VM 实例。

简单的答案:

检查数据库中现有数据库条目的代码可以使用级别足够高的数据库事务,以确保同时尝试执行相同操作的其他所有人都将被阻止并等待finish/commit 的工作。

一个非常幼稚的解决方案(假设 mysql)是 LOCK TABLES entries WRITE; 之后是逻辑,然后是 UNLOCK TABLES 当你完成时。

这也意味着在您的工作进行检查时,没有人 可以访问 table。我希望检查真的很快,因为您将在每五分钟的一小段时间内阻止对 table 的所有访问。

WRITE lock:

  • The session that holds the lock can read and write the table.
  • Only the session that holds the lock can access the table. No other session can access it until the lock is released.
  • Lock requests for the table by other sessions block while the WRITE lock is held.

来源:https://dev.mysql.com/doc/refman/5.7/en/lock-tables.html

那是一个非常无聊的答案,所以我将继续讨论您可能更感兴趣的答案...

服务器架构答案:

您希望队列中每个时间间隔只有一个作业意味着您应该只有一台机器来调度作业。使用一台专用机器最容易做到这一点,该机器仅根据计划命令分派作业。 (Laravel 5.5 引入了直接从调度程序调度作业的能力;参见 Scheduling Queued Jobs

然后你可以让几台 worker 机器处理这个队列,并且只有其中一台会拿起作业并执行它。如果一切照常进行,两台工作机器将永远不会同时执行相同的工作*。

我会将网络机器与工作机器分开,以便它们可以独立扩展。我更喜欢让我的网络机器专用于网络流量,它们不处理作业以确保任何大量排队的作业不会影响我的 http 响应时间。

因此,我建议您在设置中使用以下机器类型;

  1. 调度器 - 一台运行调度和分派作业的机器。
  2. 处理队列的工作机器。
  3. 处理访问者流量的网络机器。

所有机器都将为您的 Laravel 应用程序提供相同的源代码。它们也将具有相同的配置。唯一认为每种机器类型都是独一无二的是...

  1. 调度程序在 crontab 中有 php artisan schedule:run
  2. 工人有主管(或类似人员)负责 php artisan queue:work
  3. Web 服务器有 nginx + php-fpm 并处理传入的 Web 请求。

此设置将确保您每 5 分钟只能获得一份工作,因为只有一台机器在推送它。此设置还将确保工作人员生成的 cpu 负载不会影响 Web 请求。

我的回答有一个问题很明显;该单个调度程序机器是单点故障。如果它死了,您将不再将任何这些计划的作业分派到队列中。这涉及服务器监控和健康检查等领域,这超出了您的问题范围,并且也高度依赖于您的托管服务提供商。


关于那个小星号;我可以编造一个作业在多台机器上执行的奇怪场景。这涉及休眠时间超过超时时间的作业,同时您拥有一个不支持终止作业的环境。这将导致第一个 worker 继续执行作业(因为它无法终止它),第二个 worker 将认为该作业超时并重试。

因为 Laravel 5.6+ 您可以使用 onOneServer 函数确保您的计划任务仅在单个实例上 运行 例如

$schedule->command('loggingTask')
            ->everyFiveMinutes()
            ->onOneServer();

这需要设置APC或者Redis缓存,因为它好像使用了互斥锁,如果设置了Redis可能RedisLock

使用队列你不应该真的有这样的问题,因为从队列中弹出一个任务应该是一个原子操作。

Source