PHP + MySQL 队列

5

我需要一个简单的表格作为队列。

我的MySQL服务器限制是我不能使用InnoDB表,只能使用MyISAM。

客户端/工作者将同时工作,他们每次都需要接收不同的任务。

我的想法是按照以下方式进行(伪代码):

$job <- SELECT * FROM queue ORDER BY last_pop ASC LIMIT 1;
UPDATE queue SET last_pop WHERE id = $job->id
return $job

我尝试了表锁和"GET_LOCK",但没有任何作用,工作者有时会收到相同的任务。

3个回答

13

你需要改变排序方式,以消除时间窗口。

消费者POP(每个消费者都有唯一的$consumer_id)

Update queue 
set last_pop = '$consumer_id' 
where last_pop is null 
order by id limit 1;

$job = 
  Select * from queue 
  where last_pop = '$consumer_id' 
  order by id desc 
  limit 1;

供应商PUSH

insert into queue 
  (id, last_pop, ...) 
values 
  (NULL, NULL, ...);

队列按照id列的时间顺序进行排序,并在POP时分配给consumer_id。


我非常确定这个解决方案是错误的。UPDATE 和 SELECT 之间存在竞争条件。想象一下,你有两个并行的请求运行这段代码。如果按照以下顺序执行:UPDATE 1,UPDATE 2,SELECT 1,SELECT 2。你最终会选择相同的行。 - Oleg Kikin
1
SELECT 之前的 UPDATE 失败会导致一个孤立的项目。START TRANSACTIONCOMMIT 是必要的,以避免这种风险。 - danorton
有人可以友好地解释一下last_pop字段包含什么吗?此外,consumer_id是工作进程的唯一代码编号吗? - ethanpil
1
@ethanpil,last_pop字段在项目被认领之前将为NULL,然后在选择进行处理时将包含唯一的消费者ID。由于选择不需要与更新同时发生,因此没有时间窗口。 - Don
你们建议consumer_id在每次任务运行时更改,以便记录单个任务,还是每个worker保持一致的consumer_id,也许我们在日志中为每次尝试添加一个run_id? - ethanpil
显示剩余2条评论

1

0

Oleg,

解决方案是正确的。$consumer_id必须是处理器的唯一标识符。例如,如果您在一台机器上有几个cron作业,可以使用它们的pid作为consumer ID

UPDATE是原子性的,因此它仅将队列中的一行标记为已被您的ID消耗。

对于某些应用程序,我还有一个finished状态字段,因此,如果last_pop的consumer_id已设置,但未设置完成标志且作业已经过X时间,则它可以被标记为重新启动。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接