【发布时间】:2014-03-03 20:07:41
【问题描述】:
我有一个 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
...
Tasks 是一个用于跟踪任务的表格:
task_id 标识每个任务,times_run 是任务成功执行的次数。 task_id 是 php 脚本执行其例程所需的数字。 workers_used 是一个文本字段,其中包含已为此任务处理的所有 worker_infos 的 ID。我不希望每个任务多次使用相同的 worker_info,只需要一次。
worker_info 是一个表格,其中包含一些 php 脚本需要完成其工作的信息以及 last_used ,后者是该工作人员上次使用时间的全局指示符。
几个 php 脚本处理相同的任务,我需要精确的值,因为每个 worker_info 应该只为每个任务使用 1 次。
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 而不使用第二个表来声明依赖项是非常糟糕的做法,但我有点害怕它会占用的空间。
一个任务可以有几千个workers_used,而我自己有几千个任务。这样表格很快就会超过 100 万个条目,我担心这会大大降低速度,所以我采用了这种存储方式。
然后脚本为每个任务执行步骤 2-6 10 次,然后返回步骤 1 选择新任务并再次执行所有操作。
现在这个设置已经为我服务了大约一年,但是现在我需要在这个队列系统上激活 50 多个 php 脚本,我在性能方面遇到了越来越多的问题。 PHP 查询最多需要 20 秒,而且我无法像我需要的那样扩展,如果我只运行更多 PHP 脚本,mysql 服务器就会崩溃。 如果系统崩溃,我不希望数据丢失,因此我将每次更改都写入数据库。此外,当我创建系统时,worker_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 将自动提交设置为 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 日期就足够了?
【问题讨论】: