我有一个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日期?
答案 0 :(得分:1)
在这里阅读我关于如何在MySQL中实现作业队列的另一个问题的答案可能是有用的:
MySQL deadlocking issue with InnoDB
简而言之,使用LOCK TABLES
这是非常不必要的,不太可能产生良好的结果。