使用 table 锁 (MySQL) 防止并行执行
Prevent parallel execution using a table lock (MySQL)
我有一个名为 cronjobs 的 MySQL table,它包含所有需要的 cronjob(例如删除旧电子邮件、更新个人资料年龄等)。对于每个 cronjob 都有一个定义的代码块,如果 cronjob 到期(我对不同的 cronjobs 有不同的间隔)。
为了执行到期的 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。实际发生的是 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 table 锁吗?
非常感谢您的提前帮助,
弗雷德里克
是的,您可以使用 mysql table 锁,但这对您的情况来说可能有点矫枉过正。无论如何以最通用的方式做到这一点
- 确保你关闭了自动提交
- 锁定表 cronjobs;
- 做你的事
- 解锁牌桌
要了解确切的语法和细节,请明显阅读文档 https://dev.mysql.com/doc/refman/5.0/en/lock-tables.html,我个人从未使用过 table 级别锁定,因此可能涉及一些我不知道的陷阱。
如果您使用 InnoDB table 引擎,我会做的是使用乐观锁定:
- 在您的脚本中首先开始交易
- 获取脚本的一些 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
- 根据以上 select 返回的内容进行处理
UPDATE cronjobs set executed_by = null where executed_by = my_pid
这应该很容易做到,更容易跟踪未来发生的事情和规模(即你可以有几个实例 运行ning 运行ning 并行,只要它们执行不同的脚本)
使用此解决方案,第二个脚本不会失败(从技术上讲),它只会 运行 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)
)
How can I solve this problem, so that there are no simultaneous executions of cronjobs?
有多种方法可以解决这个问题。它们也可能有帮助:
我的建议是保持简单并使用文件锁定或文件存在检查方法。
- file_exist() + 基于 PID 的 CronHelper Class
- flock() 基于:
- 当你想避免 IO 时,将锁定状态存储到内存缓存中
- 数据库事务:见下文和@sakfa 的回答
- 使用 Redis 作为中心在分布式系统中锁定 cronjobs:https://github.com/kvz/cronlock & http://kvz.io/blog/2012/12/31/lock-your-cronjobs/
Can I use MySQL table locks?
是的,但有点矫枉过正。
您可以使用带有 cronjob 状态列("ToDo, Started, Complete" 或 "Todo, Running, Done")和 PID 列的 "cronjob processing table"。
然后你 select 作业并使用事务标记它们的状态。
这确保 "Selecting a job from Todo" 和 "marking it as running/started" 一步完成。最后,您的 "central cronjob processing script" 可能仍然有多个执行程序,但作业不会 select 多次处理。
我有一个名为 cronjobs 的 MySQL table,它包含所有需要的 cronjob(例如删除旧电子邮件、更新个人资料年龄等)。对于每个 cronjob 都有一个定义的代码块,如果 cronjob 到期(我对不同的 cronjobs 有不同的间隔)。
为了执行到期的 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。实际发生的是 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 table 锁吗?
非常感谢您的提前帮助,
弗雷德里克
是的,您可以使用 mysql table 锁,但这对您的情况来说可能有点矫枉过正。无论如何以最通用的方式做到这一点
- 确保你关闭了自动提交
- 锁定表 cronjobs;
- 做你的事
- 解锁牌桌
要了解确切的语法和细节,请明显阅读文档 https://dev.mysql.com/doc/refman/5.0/en/lock-tables.html,我个人从未使用过 table 级别锁定,因此可能涉及一些我不知道的陷阱。
如果您使用 InnoDB table 引擎,我会做的是使用乐观锁定:
- 在您的脚本中首先开始交易
- 获取脚本的一些 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
- 根据以上 select 返回的内容进行处理
UPDATE cronjobs set executed_by = null where executed_by = my_pid
这应该很容易做到,更容易跟踪未来发生的事情和规模(即你可以有几个实例 运行ning 运行ning 并行,只要它们执行不同的脚本)
使用此解决方案,第二个脚本不会失败(从技术上讲),它只会 运行 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)
)
How can I solve this problem, so that there are no simultaneous executions of cronjobs?
有多种方法可以解决这个问题。它们也可能有帮助:
我的建议是保持简单并使用文件锁定或文件存在检查方法。
- file_exist() + 基于 PID 的 CronHelper Class
- flock() 基于:
- 当你想避免 IO 时,将锁定状态存储到内存缓存中
- 数据库事务:见下文和@sakfa 的回答
- 使用 Redis 作为中心在分布式系统中锁定 cronjobs:https://github.com/kvz/cronlock & http://kvz.io/blog/2012/12/31/lock-your-cronjobs/
Can I use MySQL table locks?
是的,但有点矫枉过正。
您可以使用带有 cronjob 状态列("ToDo, Started, Complete" 或 "Todo, Running, Done")和 PID 列的 "cronjob processing table"。 然后你 select 作业并使用事务标记它们的状态。 这确保 "Selecting a job from Todo" 和 "marking it as running/started" 一步完成。最后,您的 "central cronjob processing script" 可能仍然有多个执行程序,但作业不会 select 多次处理。