避免两名工人同时进行 运行 后台作业
Avoid background job to run simultaneously by two workers
我有一个守护进程,运行我们的网络服务请求的后台作业。我们同时有 4 名工人 运行ning。
有时一个作业会同时执行两次,因为两个工人决定运行那个作业。为了避免这种情况,我们尝试了几种方法:
- 由于我们的作业来自我们的数据库,我们添加了一个名为
executed
的标志,以防止其他工作获得已经开始执行的作业;这并不能解决问题,有时我们数据库的延迟足以同时执行;
- 在系统中添加了
memcached
(同一系统中的所有工作人员 运行),但不知何故我们今天有同时工作 运行ning -- memcached
没有也解决多个服务器。
以下是我们目前使用的逻辑:
// We create our memcached server
$memcached = new Memcached();
$memcached->addServer("127.0.0.1", 11211);
// Checkup every 5 seconds for operations
while (true) {
// Gather all operations TODO
// In this query, we do not accept operations that are set
// as executed already.
$result = findDaemonOperationsPendingQuery();
// We have some results!
if (mysqli_num_rows($result) > 0) {
$op = mysqli_fetch_assoc($result);
echo "Found an operation todo #" . $op['id'] . "\n";
// Set operation as executed
setDaemonOperationAsDone($op['id'], 'executed');
// Verifies if operation is happening on memcached
if (get_memcached_operation($memcached, $op['id'])) {
echo "\tOperation id already executing...\n";
continue;
} else {
// Set operation on memcached
set_memcached_operation($memcached, $op['id']);
}
... do our stuff
}
}
这种问题一般是怎么解决的?
我在 Internet 上查找并找到了一个名为 Gearman 的库,但我不相信当我们有多个服务器时它会解决我的问题。
我想的另一件事是预定义一个守护进程 运行 插入时的操作,并创建一个故障安全的独占守护进程,运行s 由停止服务的守护进程设置的操作。
有什么想法吗?
谢谢。
您有一个典型的并发问题。
- 工人 1 读取了 table、select 一份工作
- 工人 1 更新 table 以将工作标记为 'assigned' 或其他
- 哦等等,在 1 和 2 之间,工人 2 也阅读了 table,并且由于作业尚未标记为 'assigned',工人 2 selected同样的工作
解决这个问题的方法是使用事务和锁,特别是 SELECT.. FOR UPDATE。它会像这样:
- Worker 1 启动事务 (
START TRANSACTION
) 并尝试获取独占锁 SELECT * FROM jobs [...] FOR UPDATE
- 工人 2 也这样做。除了他必须等待,因为 Worker 1 已经有了锁。
- 工人 1 更新 table 说他现在正在处理工作并立即提交事务。这会释放其他工作人员对 select 个工作的锁定。工人 1 现在可以安全地开始这项工作了。
- Worker 2 现在可以读取 table 并获取锁。由于 table 已更新,工人 2 将 select 一份不同的工作。
编辑:关于您的 PHP 代码的具体评论:
- 你的评论说你正在获取每个工人需要同时完成的所有工作。你应该只select一个,做,select一个,做,等等
- 您正在设置标志 'executed',但实际上它还没有执行。您需要一个 'assigned' 标志和一个不同的 'executed' 标志。
使用锁和事务的替代解决方案,假设每个工作人员都有一个 id。
在你的循环中 运行:
UPDATE operations SET worker_id = :wid WHERE worker_id IS NULL LIMIT 1;
SELECT * FROM operations where executed = 0 and worker_id = :wid;
更新是一个原子操作,如果尚未设置,您只需设置 worker_id,因此不用担心竞争条件。设置 worker_id 可以明确谁拥有该操作。由于 LIMIT 1,更新将只分配一个操作。
我有一个守护进程,运行我们的网络服务请求的后台作业。我们同时有 4 名工人 运行ning。
有时一个作业会同时执行两次,因为两个工人决定运行那个作业。为了避免这种情况,我们尝试了几种方法:
- 由于我们的作业来自我们的数据库,我们添加了一个名为
executed
的标志,以防止其他工作获得已经开始执行的作业;这并不能解决问题,有时我们数据库的延迟足以同时执行; - 在系统中添加了
memcached
(同一系统中的所有工作人员 运行),但不知何故我们今天有同时工作 运行ning --memcached
没有也解决多个服务器。
以下是我们目前使用的逻辑:
// We create our memcached server
$memcached = new Memcached();
$memcached->addServer("127.0.0.1", 11211);
// Checkup every 5 seconds for operations
while (true) {
// Gather all operations TODO
// In this query, we do not accept operations that are set
// as executed already.
$result = findDaemonOperationsPendingQuery();
// We have some results!
if (mysqli_num_rows($result) > 0) {
$op = mysqli_fetch_assoc($result);
echo "Found an operation todo #" . $op['id'] . "\n";
// Set operation as executed
setDaemonOperationAsDone($op['id'], 'executed');
// Verifies if operation is happening on memcached
if (get_memcached_operation($memcached, $op['id'])) {
echo "\tOperation id already executing...\n";
continue;
} else {
// Set operation on memcached
set_memcached_operation($memcached, $op['id']);
}
... do our stuff
}
}
这种问题一般是怎么解决的? 我在 Internet 上查找并找到了一个名为 Gearman 的库,但我不相信当我们有多个服务器时它会解决我的问题。
我想的另一件事是预定义一个守护进程 运行 插入时的操作,并创建一个故障安全的独占守护进程,运行s 由停止服务的守护进程设置的操作。
有什么想法吗?
谢谢。
您有一个典型的并发问题。
- 工人 1 读取了 table、select 一份工作
- 工人 1 更新 table 以将工作标记为 'assigned' 或其他
- 哦等等,在 1 和 2 之间,工人 2 也阅读了 table,并且由于作业尚未标记为 'assigned',工人 2 selected同样的工作
解决这个问题的方法是使用事务和锁,特别是 SELECT.. FOR UPDATE。它会像这样:
- Worker 1 启动事务 (
START TRANSACTION
) 并尝试获取独占锁SELECT * FROM jobs [...] FOR UPDATE
- 工人 2 也这样做。除了他必须等待,因为 Worker 1 已经有了锁。
- 工人 1 更新 table 说他现在正在处理工作并立即提交事务。这会释放其他工作人员对 select 个工作的锁定。工人 1 现在可以安全地开始这项工作了。
- Worker 2 现在可以读取 table 并获取锁。由于 table 已更新,工人 2 将 select 一份不同的工作。
编辑:关于您的 PHP 代码的具体评论:
- 你的评论说你正在获取每个工人需要同时完成的所有工作。你应该只select一个,做,select一个,做,等等
- 您正在设置标志 'executed',但实际上它还没有执行。您需要一个 'assigned' 标志和一个不同的 'executed' 标志。
使用锁和事务的替代解决方案,假设每个工作人员都有一个 id。
在你的循环中 运行:
UPDATE operations SET worker_id = :wid WHERE worker_id IS NULL LIMIT 1;
SELECT * FROM operations where executed = 0 and worker_id = :wid;
更新是一个原子操作,如果尚未设置,您只需设置 worker_id,因此不用担心竞争条件。设置 worker_id 可以明确谁拥有该操作。由于 LIMIT 1,更新将只分配一个操作。