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

时间:2017-11-02 16:21:51

标签: laravel laravel-5.4 multiple-instances

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

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

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

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

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

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

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

我不知道如何继续这里 - 虽然多个服务器,一个工作服务器设置将起作用,但我不想最终得到同一个工作的实例(来自不同的服务器)多个队列中的时间。

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

对我来说另一个有问题的是 - 如何测试?我想,我无法在本地测试它,除非我可以通过这种方式旋转彼此同步的虚拟机实例。

2 个答案:

答案 0 :(得分:6)

答案很简单:

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

一个非常天真的解决方案(假设是mysql)将是LOCK TABLES entries WRITE;后跟逻辑,然后是UNLOCK TABLES

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

  

WRITE lock:

     
      
  • 持有锁的会话可以读写表。
  •   
  • 只有持有锁的会话才能访问该表。在解除锁定之前,没有其他会话可以访问它。
  •   
  • 在保持WRITE锁定的情况下,锁定其他会话阻止对该表的请求。
  •   

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

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

服务器架构答案:

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

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

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

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

  1. 调度程序 - 运行计划并分派作业的单台计算机。
  2. 处理队列的工作机器。
  3. 处理访问者流量的网络计算机。
  4. 所有计算机的Laravel应用程序都有相同的源代码。它们也将具有相同的配置。唯一认为每种机器类型唯一的是......

    1. 调度程序在crontab中有php artisan schedule:run
    2. 工作人员拥有运行php artisan queue:work的主管(或类似的东西)。
    3. 网络服务器有nginx + php-fpm并处理传入的网络请求。
    4. 此设置将确保您每5分钟只能获得一份作业,因为只有一台机器正在推动它。此设置还将确保工作程序生成的cpu负载不会影响Web请求。

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

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

答案 1 :(得分:3)

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

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

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

使用队列,您实际上应该不会有这样的问题,因为从队列弹出任务应该是原子操作。

Source