这基本上应该被视为两个独立的问题:
- 为每个工人寻找一份工作来处理。理想情况下,这应该非常有效,并在接下来的第 2 步中预先避免失败。
- 确保每个作业最多处理一次或仅处理一次。无论发生什么情况,同一个作业都不应该由多个工人同时处理。您可能希望确保不会因为有车/撞车的工人而失去工作。
这两个问题都有多种可行的解决方案。我会就我的偏好给出一些建议:
找工作来处理
对于低速系统,只需查找最近的未处理作业就足够了。您还不想接受这份工作,只需将其确定为候选人即可。这可能是:
SELECT id FROM jobs ORDER BY created_at ASC LIMIT 1
(请注意,这将首先处理最旧的作业——先进先出顺序——并且我们假设行在处理后被删除。)
申请工作
在这个简单的例子中,这将很简单(注意我正在避免一些可能使事情变得不太清楚的优化):
BEGIN;
SELECT * FROM jobs WHERE id = <id> FOR UPDATE;
DELETE FROM jobs WHERE id = <id>;
COMMIT;
如果SELECT
查询时返回我们的工作id
,我们现在已经锁定它。如果另一个工人已经接受了这个工作,则会返回一个空集,我们应该寻找另一个工作。如果两个工人竞争同一个工作,他们会从一开始就互相阻止SELECT ... FOR UPDATE
,这样前面的陈述就普遍成立了。这将允许您确保每个作业最多处理一次。然而...
只处理一次作业
先前设计中的一个风险是工作人员接受了一项工作,但未能处理它,然后崩溃。现在失去了这份工作。因此,大多数作业处理系统在他们声称作业时不会删除作业,而是将其标记为由某个工人声称并实施作业回收系统。
job
这可以通过使用表中的附加列或单独的表来跟踪索赔本身来实现claim
。通常会写一些关于工作人员的信息,例如主机名、PID 等,( claim_description
) 并为声明提供一些到期日期 ( claim_expires_at
),例如未来 1 小时。然后,一个额外的过程会处理这些索赔,并以事务方式释放过期的索赔 ( claim_expires_at < NOW()
)。claim_expires_at IS NULL
然后,申请工作还需要在选择时和申请时检查工作行是否有申请 ( ) SELECT ... FOR UPDATE
。
请注意,这个解决方案仍然存在问题:如果一个作业处理成功,但工人在成功将作业标记为已完成之前崩溃了,我们最终可能会释放索赔并重新处理该作业。解决这个问题需要一个更高级的系统,作为练习留给读者。;)