关于队列系统的问题

时间:2014-03-03 20:07:42

标签: php mysql queue innodb

我有一个mysql队列,可以管理通过cron作业每分钟运行的几个php工作人员的任务。 我将简化一切以使其更容易理解。

对于mysql部分,我有2个表:

worker_info

worker_id  |  name    | hash      | last_used
1          |  worker1 | d8f9zdf8z | 2014-03-03 13:00:01
2          |  worker2 | odfi9dfu8 | 2014-03-03 13:01:01
3          |  worker3 | sdz7std74 | 2014-03-03 13:02:03
4          |  worker4 | duf8s763z | 2014-03-03 13:02:01
...

tasks

task_id  | times_run | task_id | workers_used
1        | 3         | 2932    | 1,6,3
2        | 2         | 3232    | 6,8
3        | 6         | 5321    | 3,2,6,10,5,20
4        | 1         | 8321    | 3
...

任务是一个跟踪任务的表:

task_id标识每个任务,times_run是任务成功执行的次数。 task_id是php脚本其例程所需的数字。  workers_used是一个文本字段,用于保存为此任务处理的所有worker_infos的id。我不希望每个任务多次使用相同的worker_info,只需要一次。

worker_info是一个表,其中包含php脚本需要执行其工作的一些信息以及last_used,这是上次使用此工作时的全局指示符。

几个php脚本处理相同的任务,我需要精确的值,因为每个worker_info每次任务只能使用一次。

PHP cron作业包括所有相同的例程:

脚本执行mysql查询以获取任务。

1. SELECT * FROM tasks ORDER BY times_run ASC LIMIT 1我们一直在做一份工作

该脚本锁定了worker_info表,以避免从任务查询中多次选择一个worker_info

2. LOCK TABLES worker_info WRITE

然后它获取一个未用于此任务的所有worker_infos的列表,按last_used排序

3. SELECT * FROM worker_info WHERE worker_id NOT IN($workers_used) ORDER BY last_used ASC LIMIT 1

然后它更新last_used参数,以便在任务仍然运行时同时不会选择相同的worker_info

4. UPDATE workder_info Set last_used = NOW() WHERE worker_id = $id

最后锁被释放

5. UNLOCK TABLES

php脚本执行其例程,如果任务成功,则会更新

6. UPDATE tasks SET times_run = times_run + 1, workers_used = IF(workers_used = '', '$worker_id', CONCAT(workers_used,', $worker_id'))我知道以这种方式执行workers_used不使用第二个表来声明依赖关系是非常糟糕的做法,但我有点害怕它需要的空间。 一个任务可以有几千个worker_used,我自己有几千个任务。通过这种方式,这个表会很快变得超过100万个条目,我担心这可能会减慢很多东西,所以我采用这种存储方式。

然后脚本为每个任务执行步骤2-6 10次,然后返回步骤1选择新任务并再次执行所有操作。

现在这个设置已经为我提供了大约一年的时间,但是现在我需要在这个队列系统上运行50多个php脚本,我在性能方面遇到了越来越多的问题。 PHP查询需要长达20秒,我不能再像我需要的那样扩展,如果我只运行更多PHP脚本,mysql服务器崩溃了。 如果系统崩溃,我希望没有数据丢失,因此我正在将所有更改写入数据库中。另外,当我创建系统时,我遇到了workers_used的问题,因为当10个php脚本在1个任务上工作时,经常发生一个worker_info数据在我不想要的同一个任务中被多次使用。

因此我介绍了修复此问题的LOCK,但我怀疑它是系统的瓶颈。如果一个工作者锁定表以执行其操作,则所有其他49个php工作者需要等待那个坏的。

现在我的问题是:

这种实施甚至是好的吗?我应该坚持下去还是把它扔掉并做点别的事情?

这个LOCK甚至是我的问题还是其他什么东西可能会减慢系统的速度?

如何改进此设置以加快速度?

//编辑按照jeremycole的建议:

我想我需要更新worker_info表以实现更改:

worker_info

worker_id  |  name    | hash       | tasks_owner | last_used
1          |  worker1 | d8f9zdf8z  | 1           | 2014-03-03 13:00:01
2          |  worker2 | odfi9dfu8  | NULL        | 2014-03-03 13:01:01
3          |  worker3 | sdz7std74  | NULL        | 2014-03-03 13:02:03
4          |  worker4 | duf8s763z  | NULL        | 2014-03-03 13:02:01
...

然后将例程更改为:

SET autocommit=0将autocommit设置为0,这样查询就不会自动提交

1. SELECT * FROM tasks ORDER BY times_run ASC LIMIT 1选择要处理的任务

2. START TRANSACTION

3. SELECT * FROM worker_info WHERE worker_id NOT IN($workers_used) AND tasks_owner IS NULL ORDER BY last_used ASC LIMIT 1 FOR UPDATE

4. UPDATE worker_info SET last_used = NOW(), tasks_owner = $task_id WHERE worker_id = $worker_id

5. COMMIT

执行PHP例程,如果成功:

6. UPDATE tasks SET times_run = times_run + 1, workers_used = IF(workers_used = '', '$worker_id', CONCAT(workers_used,', $worker_id'))

那应该是它还是我错了? task_owner真的需要,还是足以改变last_used日期?

1 个答案:

答案 0 :(得分:1)

在这里阅读我关于如何在MySQL中实现作业队列的另一个问题的答案可能是有用的:

MySQL deadlocking issue with InnoDB

简而言之,使用LOCK TABLES这是非常不必要的,不太可能产生良好的结果。