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

eit*_*hed 2 multiple-instances laravel laravel-5.4

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

嗯 - 我错了,那种情况确实发生了:

  • A - 检查日志是否存在 - 它不存在
  • B - 检查日志是否存在 - 它不存在
  • A - 创建日志
  • B - 创建日志
  • A - 发送消息
  • B - 发送消息

我已经改变了这种行为来引入队列,这应该可以缓解这个问题.虽然crons仍然会开火,但是多个工作将排队,工作人员应该在不同时间接收给定的工作,从而防止两次发送消息.虽然它最终可能会成为:

  • A - 拿起工作1
  • B - 拿起工作2
  • A - 检查日志是否存在 - 它不存在
  • B - 检查日志是否存在 - 它不存在

等等或A和B也可能在同一时间完成相同的工作.

我想,解决方案是运行一个工作服务器.但后来我发现来自多个服务器的作业排队多次,我无法检查它们是否已经排队,因为我们最终会遇到第一个场景.

我不知道如何继续这里 - 虽然多个服务器,一个工作服务器设置将工作,我不想在队列中多次结束相同作业的实例(来自不同的服务器).

也许解决方案是拥有一个cron/queue/worker服务器,但我没有使用Laravel/multiserver环境进行设置的经验.

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

sis*_*sve 7

答案很简单:

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

一个真正天真的解决方案(假设mysql)将LOCK TABLES entries WRITE;遵循逻辑,然后UNLOCK TABLES当你完成.

这也意味着在您的工作进行检查时,没有人可以访问该表.我希望检查非常快,因为你将每隔五分钟阻止对该表的所有访问.

WRITE锁:

  • 持有锁的会话可以读写表.
  • 只有持有锁的会话才能访问该表.在释放锁之前,没有其他会话可以访问它.
  • 在保持WRITE锁定时,其他会话阻止对表的请求.

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

这是一个非常无聊的答案,所以我将继续回答你可能更感兴趣的答案......

服务器架构答案:

您希望队列中每个时间间隔只有一个作业意味着您应该只有一台计算机调度作业.使用一台仅从预定命令调度作业的专用机器,这是最简单的方法.(Laravel 5.5引入了直接从调度程序调度作业的功能;请参阅调度排队作业)

然后,您可以让几台工作人员机器处理队列,其中只有一台机器会接收作业并执行它.如果一切正常*,两台工人机器将永远不会同时执行相同的工作.

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

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

  1. 调度程序 - 运行计划并分派作业的一台计算机.
  2. 处理队列的工作机器.
  3. 处理访问者流量的Web机器.

所有机器都将具有相同的Laravel应用程序源代码.它们也将具有相同的配置.每种机器类型唯一的想法是......

  1. 调度程序php artisan schedule:run位于crontab中.
  2. 工人有主管(或类似的东西)运行php artisan queue:work.
  3. Web服务器具有nginx + php-fpm并处理传入的Web请求.

此设置将确保您每5分钟只能获得一个作业,因为只有一台机器正在推动它.此设置还将确保工作程序生成的cpu负载不会影响Web请求.

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


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


apo*_*fos 5

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

$schedule->command('loggingTask')
            ->everyFiveMinutes()
            ->onOneServer();
Run Code Online (Sandbox Code Playgroud)

这需要设置 APC 或 Redis 缓存,因为它似乎使用了互斥锁,RedisLock如果设置了 Redis。

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

来源