Laravel 5.5 MySQL-based "queue" 在高请求量时锁定数据库

Laravel 5.5 MySQL-based "queue" locks up the database at high request-volume

我 运行 遇到了一个我无法理解的关于 MySQL 数据库事务、锁定和 Laravel 的 Eloquent ORM 的问题。在此先感谢您的帮助。

我的(简化的)问题是这样的:我的 MySQL 数据库中有一个 tasks table,一个用于检索下一个任务的端点,以及一个用于标记任务的端点完成或不活动。 tasks table 旨在充当一种队列,允许用户提取当前没有其他人正在处理的下一个可用任务(阅读:任务处于非活动状态)。如果任务被拉取但 3 小时内没有更新,它应该再次对检索端点可见(以防用户意外退出应用等)。

我的问题出在任务检索端点上。我在这里使用 DB::transactionlockForUpdate() 来防止并发问题(即两个用户在标记为活动之前获取相同的任务)。当每秒请求量相对较低时,它会按预期工作,为每个请求正确获取唯一任务。但是,在更高的请求频率下,整个 tasks table 最终会锁定,请求开始超时,并且数据库变得无响应,直到重新启动。这是我的代码:

// Select the next task in the queue
$task = DB::transaction(
    function () {
        $t = Task::where('finished', '=', false)
            ->where(function ($q) {
                // Task must not be active for another user
                $q->where('active', '=', false)
                    ->orWhere('updated_at', '<', DB::raw('DATE_SUB(NOW(), INTERVAL 3 HOUR)'));
            })
            // Prefer tasks that haven't been seen yet
            ->orderBy('attempts')
            ->lockForUpdate()
            ->first();

        if (null !== $t) {
            // Set the task as active and increment attempts
            $t->active = true;
            $t->attempts++;
            $t->save();
        }

        return $t;
    }
);

一些额外的上下文:

我在这里明显遗漏了什么,还是我完全偏离了基地?我正在尝试的是可能的,还是我需要从 MySQL 转移到更传统的排队系统(Redis、SQS 等)?如果可以的话,我可以毫无问题地切换到原始 SQL 查询而不是使用 ORM。感谢您的指导!

我不确定,但建议太长,无法发表评论。

我认为lockForUpdate方法可能会导致该行被锁定即使在下次搜索时考虑。这意味着在提交上一个更新之前,一个搜索无法完成。 (即在第一次搜索中你 select 行 A 是非活动的,然后在第二次搜索中你寻找所有非活动行,其中行 A 仍然是一个,但你不能完成查询直到行 A已解锁)。

要解决此问题,您可以尝试添加 SKIP LOCKED(请参阅 docs)。这会将锁定的行 A 从第二次搜索中排除,并允许它在提交前一个事务之前得到 return 个结果。

我在 Laravel 中看不到添加它的方法,因此您需要使用原始查询构建器手动添加锁定子句,或者 ->whereRaw 可能会起作用。