【问题标题】:Simultaneous running cron jobs records processing mix同时运行的 cron 作业记录处理组合
【发布时间】:2021-01-16 21:55:10
【问题描述】:

我正在为多个同时午餐的 cronjobes 相同的记录过程问题而苦苦挣扎

简介

  • 我有 100 多个客户的 db 表
  • 对于每个客户,我必须每 5 分钟通过 cronjobe 运行一些脚本
  • Cronjobe 从 db 中获取每个客户立即更新上次使用的时间戳,以便下一个 cron 将选择 5 分钟前处理的下一条记录
  • Cronjobe 处理大约需要 30 秒
  • 所以这意味着在所需的 5 分钟内处理所有 100 个客户,我将需要大约 10 次模拟 cron jobes wokring
  • 所有 cron 定义为:* * * * * php cron.php(因此每 1 分钟午餐 10 个 cronjobes)
  • cron 对该客户 ip 进行一些网络查找并将其记录到日志数据库中,它应该在每 5 分钟内为每个客户发生一次,因为稍后我将根据每个客户的日志绘制图表
  • 代码用PHP编写,DB是MySql

问题是当 10 个 cronjobes 同时启动时,随机发生 2 个 cronjobes 从数据库中选择同一个客户进行处理

所以这意味着 2 个 crons 几乎同时开始(微秒差异) 两者都是同时从数据库中选择最后一个未处理的行(例如 id:17),然后更新数据库中的相同 id17 3-dth 午餐 cronjobe 已经使用了 id:18。 但我需要每个 coronjobe 从数据库中获取不同的唯一下一条记录

As a workaround i tried to add random sleep(rand(1,10)) delay at beginning of cron.php but don't help mutch, random duplication still happens cause cron continuously selects last unprocessed next customer which sometime matches with another cornjobe next customer select at same time

有没有针对这种情况的解决方案???

【问题讨论】:

  • 您可以将每个客户永久分配到十个 cron 作业之一。这样,客户总是由同一个工作处理,不会发生冲突。除了这样一个基本的解决方案:这一切听起来很奇怪。您真的需要每 5 分钟花费 30 秒处理每个客户的时间吗?这让我想知道这是怎么回事。对于更好的解决方案,最好有一个想法,我也很好奇。
  • @KIKOSoftware 感谢您的评论,实际上,客户和 crons 数量将不断增加,很难将每个客户优化分配给其个人玉米作业。 cron 对该客户 ip 进行一些网络查找并将其记录到日志数据库中,它应该在每 5 分钟内为每个客户发生一次,因为稍后我将根据日志绘制图表 - 这个想法是让许多 cronjobes 随机选择最后一个未处理的客户和流程
  • 将客户分配给 cron 作业并不难。假设您有 10 个工作,编号为 0 到 9,以及 200 个客户,唯一 ID 为 0 到 199。您可以像这样分配客户:$jobNo = $customerID % 10;。同样,一个 cron 作业可以简单地选择每 10 个客户。这并不难做到。
  • @KIKOSoftware aha 我明白了,谢谢,通过 %id 掩码选择的好方法,但在这种情况下,如果管理员说删除了所有与掩码 %5 匹配 cron 的客户,那么第 5 个 cronjobe 将拥有更少然后其他人要做的工作(而不是他可以帮助他人),解决方案将再次不是最佳的,但是是的,我们将避免重复
  • 是的,被删除的客户会带来轻微的挑战,但我认为这是可以克服的。这个基本思想保持不变:让客户永远不会被多个 cron 作业选中。

标签: php mysql cron


【解决方案1】:

您寻求的解决方案是协作锁定。客户必须被某个作业标记为“锁定”,并且任何脚本都不能选择被另一个脚本锁定的客户。

此外,您必须确保没有两个工作会选择同一个客户来获取。

在 MySQL 中你可以这样做:

$me = getmypid();
$conn->execute("SELECT GET_LOCK('choosing', 5) AS okay");
// Check the returned value of okay. If it is not 1, exit() immediately.
// choose some customer in some way. The smallest Id with OwnedByPid=0 for example. The query should be fast enough to run in under 5 seconds.
$conn->execute("UPDATE customers SET OwnedByPid={$me} WHERE id={$custId};");
$conn->execute("SELECT RELEASE_LOCK('choosing'");
//

// Do your work

$conn->execute("SELECT GET_LOCK('choosing', 5)");
$conn->execute("UPDATE customers SET OwnedByPid=0 WHERE OwnedByPid={$me};");
$conn->execute("SELECT RELEASE_LOCK('choosing')");

然后,定期 - 当没有脚本运行时 - 释放可能被崩溃的脚本标记的客户:

$conn->execute("UPDATE customers SET OwnedByPid=0;");

或者您可以添加另一列 OwningStart,在您获得所有权时将其设置为 NOW(),这样您就可以检查 OwningStart 何时超过 30 秒并清除它。或将其标记为免费:

SELECT MIN(Id) FROM customers WHERE OwnedByPid=0 OR OwningStart < NOW() - INTERVAL 2 MINUTE;

【讨论】:

  • 谢谢,看起来像我正在寻找的解决方案!只有一个问题 SELECT GET_LOCK('choosing', 5) 意味着如果另一个 cron 将运行相同的 SELECT GET_LOCK('choosing', 5) 他将得到空结果,这意味着我需要在该 cron 中有某种无限循环会不断检查“选择”,一旦它释放,就会自己锁定并记录对吗?
  • 不,GET_LOCK 正在阻塞(最多请求 5 秒)。在这段时间内,运行查询以选择客户 ID 已经足够了,所有其他运行 GET_LOCK 的 cron 作业都将停止并等待。所以他们都将继续排队。如果第一部分在 0.01 秒或更短的时间内执行,很可能,那么 10 个作业最多将等待额外的十分之一秒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-13
  • 1970-01-01
  • 2014-11-04
  • 2017-04-20
  • 1970-01-01
  • 2011-06-16
相关资源
最近更新 更多