我有一个守护程序,它运行我们的Web服务请求的后台作业。我们有4名工人同时运行。
有时候一份工作同时执行两次,因为两名工人决定执行这项工作。为了避免这种情况,我们尝试了几件事:
executed
的标志,可防止其他作品获得已经开始执行的作业;这并不能解决问题,有时我们的数据库的延迟足以同时执行; memcached
(所有工作人员都在同一系统中运行),但不知怎的,我们今天同时运行了作业 - 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
}
}
这种问题通常如何解决? 我抬头看了一下互联网,发现了一个叫做Gearman的图书馆,但是当我们有多台服务器时,我不相信它会解决我的问题。
我想到的另一件事是预定义一个守护进程在插入时运行该操作,并创建一个故障安全独占守护进程,该守护进程运行由服务中断的守护进程设置的操作。
有什么想法吗?
感谢。
答案 0 :(得分:2)
您有典型的并发问题。
解决此问题的方法是使用事务和锁,尤其是SELECT.. FOR UPDATE。它会是这样的:
START TRANSACTION
)并尝试获取排他锁SELECT * FROM jobs [...] FOR UPDATE
编辑:关于PHP代码的具体评论:
答案 1 :(得分:2)
使用锁和事务的替代解决方案,假设每个工作者都有一个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,更新将仅分配一个操作。