避免后台作业由两个工作进程同时运行

x8goxv8g  于 2021-06-21  发布在  Mysql
关注(0)|答案(2)|浏览(334)

我有一个守护进程,它运行我们的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的库,但我不相信当我们有多个服务器时,它能解决我的问题。
我想的另一件事是预定义一个守护进程来运行插入时的操作,并创建一个故障保护独占守护进程来运行由服务中断的守护进程设置的操作。
有什么想法吗?
谢谢。

wljmcqd8

wljmcqd81#

您有一个典型的并发问题。
worker 1读取表,选择一个作业
worker 1更新表以将作业标记为“assigned”或其他内容
哦,等等,在1和2之间,工人2也读了表,由于作业还没有标记为“已分配”,工人2选择了相同的作业
解决这个问题的方法是使用事务和锁,特别是select。。更新。会是这样的:
辅助进程1启动事务( START TRANSACTION )并试图获得一个独占锁 SELECT * FROM jobs [...] FOR UPDATE 工人2也这样做。但他必须等待,因为工人1已经有锁了。
worker 1更新表,表示他正在处理作业,并立即提交事务。这将释放其他工人选择作业的锁定。工人1现在可以安全地开始从事这项工作。
worker 2现在可以读取表并获取锁。由于表已更新,辅助进程2将选择一个不同的作业。
编辑:有关php代码的特定注解:
您的评论说,您正在获取每个工人需要同时完成的所有工作。你应该只选择一个,做它,选择一个,做它,等等。
您正在设置标志“executed”,而实际上它尚未执行。您需要一个“已分配”标志和一个不同的“已执行”标志。

tv6aics1

tv6aics12#

另一种使用锁和事务的解决方案,假设每个工作进程都有一个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\u id,如果它还没有设置,那么就不用担心争用条件了。设置worker\u id可以明确操作的所有者。由于限制1,更新将只分配一个操作。

相关问题