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

Avoid background job to run simultaneously by two workers

我有一个守护进程,运行我们的网络服务请求的后台作业。我们同时有 4 名工人 运行ning。

有时一个作业会同时执行两次,因为两个工人决定运行那个作业。为了避免这种情况,我们尝试了几种方法:

  1. 由于我们的作业来自我们的数据库,我们添加了一个名为 executed 的标志,以防止其他工作获得已经开始执行的作业;这并不能解决问题,有时我们数据库的延迟足以同时执行;
  2. 在系统中添加了 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. 工人 1 读取了 table、select 一份工作
  2. 工人 1 更新 table 以将工作标记为 'assigned' 或其他
  3. 哦等等,在 1 和 2 之间,工人 2 也阅读了 table,并且由于作业尚未标记为 'assigned',工人 2 selected同样的工作

解决这个问题的方法是使用事务和锁,特别是 SELECT.. FOR UPDATE。它会像这样:

  1. Worker 1 启动事务 (START TRANSACTION) 并尝试获取独占锁 SELECT * FROM jobs [...] FOR UPDATE
  2. 工人 2 也这样做。除了他必须等待,因为 Worker 1 已经有了锁。
  3. 工人 1 更新 table 说他现在正在处理工作并立即提交事务。这会释放其他工作人员对 select 个工作的锁定。工人 1 现在可以安全地开始这项工作了。
  4. 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,更新将只分配一个操作。