我有一个名为 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 就不会导致并行执行相同的 cronjobs 已经被执行。
现在的问题是:有时执行需要超过 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 表锁吗?
非常感谢您的提前帮助,
弗雷德里克
最佳答案
是的,你可以使用 mysql 表锁,但这对你的情况来说可能有点过分了。无论如何以最通用的方式做到这一点
- 确保你关闭了自动提交
- 锁定表 cronjobs;
- 做你的事
- 解锁表格
要了解确切的语法和细节,请明显阅读文档 https://dev.mysql.com/doc/refman/5.0/en/lock-tables.html ,我个人从未使用过表级锁定,所以可能有一些我不知道的陷阱。
如果您使用 InnoDB 表引擎,我会做的是使用乐观锁定:
- 在脚本中首先开始事务
- 获取一些脚本 ID 或其他任何东西,可能是进程 pid (
getmypid()
) 或主机 + 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)
)
关于php - 使用表锁防止并行执行 (MySQL),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30840746/