我有一个mysql
队列,它管理几个php
工人的任务,这些工人通过cron工作每分钟运行一次。 我会简化一切,使其更容易理解。关于队列系统的问题
对于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一起完成工作的一些信息,last_used是此工作人员上次使用时的全局指示符。
几个php脚本工作在相同的任务,我需要的值是精确的,因为每个worker_info应该只用于每个任务1次。
的PHP cron作业包括所有相同的套路:
脚本执行MySQL查询得到的任务。
1. SELECT * FROM tasks ORDER BY times_run ASC LIMIT 1
我们一直在与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数据在我不想要的同一个任务中被多次使用。
所以我介绍了这个固定的锁,但我怀疑它是系统的瓶颈。如果一名工作人员锁定桌面来执行其操作,则所有其他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
将自动提交设置为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'))
这应该是它还是我错在某些时候? 是否真的需要tasks_owner还是足以改变last_used日期?
谢谢,我注意到你从你一些其他的答案,并希望你在这里回答为好。我已经更新了我的初始文章,实施了您在其他文章中建议的例程。你能检查一下它是否正确吗? – maddo7
附加说明:我不明白你的观点3.2:如何检查一个条目是否不再无人认领? – maddo7
附加注释II:我只是用包含一个ID,一个数字和一个日期的表格进行测试,并用各种条目填充它。然后我在2个php文件中同时执行这些查询:'SET autocommit = 0; BEGIN TRANSACTION; SELECT * FROM mysql_tests ORDER BY date date ASC FOR UPDATE; UPDATE mysql_tests SET count = count + 1 WHERE tid = 1; COMMIT;'I added提交前10秒延迟,所以我可以看到会发生什么。结果是一个带有查询的脚本在执行查询之前等待另一个脚本完成,因此它们都在同一行上工作。有没有一种方法, – maddo7