1

我有一个名为 cronjobs 的 MySQL 表,它包含每个需要的 cronjob 的完整信息(例如删除旧电子邮件、更新个人资料年龄等)。对于每个 cronjob 都有一个定义的代码块,如果 cronjob 到期(我对不同的 cronjobs 有不同的时间间隔),就会执行该代码块。

为了执行到期的 cronjobs,我得到了一个由 UNIX crontab 每分钟执行一次的 PHP 脚本(调用 execute_cronjobs_due.sh,它调用“php -f /path/to/file/execute_cronjobs_due.php”)。

当执行 execute_cronjobs_due.php 时,所有的 cronjobs 都会被标记为它们将被执行,因此另一个 execute_cronjobs_due.php 调用不会导致同一个 cronjob 的并行执行已经被执行。

现在的问题是:有时执行需要超过 60 秒,但 crontab 程序在这 60 秒后没有调用 execute_cronjobs_due.sh。实际发生的是 execute_cronjobs_due.sh 在执行前一个 crontab 之后立即被调用。如果一次执行时间超过 120 秒,接下来的两次执行将同时初始化。

时间线:

2015-06-15 10:00:00:执行 execute_cronjobs_due.sh(需要 140 秒)

2015-06-15 10:02:20:execute_cronjobs_due.sh 的两个同时执行

由于它是同时执行的,因此没有使用标记它们正在执行的 cronjob,因为选择(实际上应该排除标记的一次)是在完全相同的时间执行的。因此,更新发生在两者都已经选择了到期的 cronjobs 之后。

我该如何解决这个问题,以使 cronjobs 不会同时执行?我可以使用 MySQL 表锁吗?

非常感谢您提前提供的帮助,

弗雷德里克

4

2 回答 2

2

是的,您可以使用 mysql 表锁,但这对您的情况来说可能是多余的。无论如何,以最通用的方式做到这一点

  1. 确保您已关闭自动提交
  2. 锁定表 cronjobs;
  3. 做你的事
  4. 解锁桌子

有关确切的语法和详细信息,请阅读文档https://dev.mysql.com/doc/refman/5.0/en/lock-tables.html,我个人从未使用过表级锁定,所以可能涉及一些问题我不是意识到。

如果您使用 InnoDB 表引擎,我会做的是使用乐观锁定:

  1. 在您的脚本中首先启动事务
  2. 获取脚本的一些 id 或其他任何东西,可能是进程 pid ( getmypid()) 或主机 + pid 的组合。或者如果您不知道哪个是完美的,则只生成 guid
  3. 做类似的事情UPDATE cronjobs SET executed_by = my_id WHERE executed_by is null and /* whatever condition to get jobs to run */
  4. 然后SELECT * FROM cronjobs where executed_by = my_pid
  5. 在上面选择返回的任何内容上做你的事情
  6. UPDATE cronjobs set executed_by = null where executed_by = my_pid

这应该很容易做到,更容易跟踪将来发生的事情和扩展(即,只要它们执行不同的脚本,您就可以让少数实例并行运行)

使用此解决方案,第二个脚本不会失败(技术上),它只会运行 0 个作业。

减号是您必须清理已声明但脚本未能将它们标记为已完成的作业,但您可能必须使用当前解决方案来执行此操作。最简单的方法是添加一个时间戳列,该列将跟踪上次申请作业的时间并在 15 分钟或一小时后过期,具体取决于业务需求(短伪代码:第一次更新即可SET executed_by = my_id, started_at = NOW() where executed_by is null or (executed_by is not null and started_at < NOW() - 1 hour)

于 2015-06-15T09:14:27.050 回答
0

我该如何解决这个问题,以使 cronjobs 不会同时执行?

有多种方法可以解决这个问题。它们也可能会有所帮助:

我的建议是保持简单并使用文件锁定或文件存在检查方法。

我可以使用 MySQL 表锁吗?

是的,但它有点矫枉过正。

您将使用带有 cronjob 状态列(“ToDo, Started, Complete”或“Todo, Running, Done”)和 PID 列的“cronjob 处理表”。然后您选择作业并使用事务标记它们的状态。这样可以确保“从 Todo 中选择作业”和“将其标记为正在运行/已启动”是一步完成的。最后,您的“中央 cronjob 处理脚本”可能仍然有多个 exec,但不会多次选择作业进行处理。

于 2015-06-15T09:47:01.983 回答