避免后台工作由两名工人同时运行

时间:2018-01-17 15:01:07

标签: php mysql concurrency daemon

我有一个守护程序,它运行我们的Web服务请求的后台作业。我们有4名工人同时运行。

有时候一份工作同时执行两次,因为两名工人决定执行这项工作。为了避免这种情况,我们尝试了几件事:

  1. 由于我们的工作来自我们的数据库,我们添加了一个名为executed的标志,可防止其他作品获得已经开始执行的作业;这并不能解决问题,有时我们的数据库的延迟足以同时执行;
  2. 在系统中添加memcached(所有工作人员都在同一系统中运行),但不知怎的,我们今天同时运行了作业 - memcached也无法解决多个服务器问题。
  3. 以下是我们目前使用的逻辑:

    // 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的图书馆,但是当我们有多台服务器时,我不相信它会解决我的问题。

    我想到的另一件事是预定义一个守护进程在插入时运行该操作,并创建一个故障安全独占守护进程,该守护进程运行由服务中断的守护进程设置的操作。

    有什么想法吗?

    感谢。

2 个答案:

答案 0 :(得分:2)

您有典型的并发问题

  1. 工人1读取表格,选择作业
  2. 工作人员1更新表格以将作业标记为“已分配”'或者其他什么
  3. 哦,等等,在1到2之间,工人2也阅读了表格,由于工作尚未标记为“工作人员”,工人2选择了相同的工作
  4. 解决此问题的方法是使用事务和锁,尤其是SELECT.. FOR UPDATE。它会是这样的:

    1. 工人1启动交易(START TRANSACTION)并尝试获取排他锁SELECT * FROM jobs [...] FOR UPDATE
    2. 工人2也是如此。除非他必须等待,因为工人1已经有锁。
    3. 工作人员1更新表格,表示他现在正在处理工作并立即提交交易。这将释放锁定以供其他工作人员选择作业。工人1现在可以安全地开始从事这项工作。
    4. 工人2现在可以读取该表并获取锁定。由于表已更新,因此工作人员2将选择不同的作业。
    5. 编辑:关于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,更新将仅分配一个操作。