我有一个名为cronjobs的MySQL表,其中包含所需的每个cronjob的entires(例如删除旧电子邮件,更新配置文件时间等)。对于每个cronjob都有一个定义的代码块,如果cronjob到期则执行该代码块(不同的cronjobs有不同的间隔)。
为了执行due cronjobs,我得到了一个PHP脚本,每分钟由UNIX crontab执行(调用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。实际发生的是在执行前一个crontab之后立即调用execute_cronjobs_due.sh。如果执行时间超过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表锁吗?
非常感谢您的帮助,
弗雷德里克
答案 0 :(得分:2)
是的,您可以使用mysql表锁,但这对您的情况可能有点过分。无论如何以最通用的方式做到这一点
有关确切的语法和详细信息,请阅读文档obviusly https://dev.mysql.com/doc/refman/5.0/en/lock-tables.html,我个人从未使用过表级锁定,所以可能会涉及一些我不知道的捕获。
如果您使用InnoDB表引擎,我会做的是采用乐观锁定:
getmypid()
)或host + pid的组合。或者只是生成guid,如果你不知道哪个是完美的UPDATE cronjobs SET executed_by = my_id WHERE executed_by is null and /* whatever condition to get jobs to run */
SELECT * FROM cronjobs where executed_by = my_pid
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)
)
答案 1 :(得分:0)
如何解决此问题,以便不会同时执行cronjobs?
有多种方法可以解决这个问题。它们可能也有帮助:
我的建议是保持简单并使用文件锁定或文件存在的检查方法。
我可以使用MySQL表锁吗?
是的,但这有点矫枉过正。
你会使用&#34; cronjob处理表&#34;使用cronjob状态列(&#34; ToDo,Started,Complete&#34;或&#34; Todo,Running,Done&#34;)和PID列。 然后选择作业并使用事务标记其状态。 这确保&#34;从Todo&#34;中选择一份工作。并且&#34;将其标记为正在运行/已启动&#34;一步完成。最后,您可能仍然拥有多个中央cronjob处理脚本&#34;的执行,但是多次未选择作业进行处理。