PHP队列执行任务的时候,如何防止进程之间抢夺资源?

PHP队列执行任务的时候,如何防止进程之间抢夺资源?
比如视频转码队列,每分钟会起一个进程去处理视频转码任务,每次从数据库获取10条
进程1已经获取了ID为10的视频去处理,一分钟后,如何防止进程2 拿到其他进程已经处理过的?
处理完成后,有异步通知,但是异步通知会有延迟,所以视频状态有可能修改不及时

阅读 5.6k
4 个回答

实际上有一个非常简单的办法,你可以利用数据库操作的原子性来实现,不需要那么复杂的锁机制,甚至队列。就按你的方法来,假设任务数据表 task 里有两个字段 id, status,我们定义status三个状态

0: 待处理
1: 正在处理
2: 处理完成

假设你有一堆 PHP 进程都用如下 SQL 语句去取出数据库里的待处理任务

SELECT * FROM task WHERE status = 0

取出来以后,我们为了防止其他用户不再重复取出要把它的状态标记为 1

UPDATE task SET status = 1 WHERE id = xxx

但是等等,这样就会产生如你所说的资源抢夺,但如果加上一个简单的技巧就可以避免,你把语句变成这样

UPDATE task SET status = 1 WHERE id = xxx AND status = 0

熟悉一点数据库的人可能会说这样还是避免不了抢夺,只是避免了重复写入。
我要说的是,能避免重复写入就够了,我们的进程在执行完这条操作后,去获取 affected_rows ,即更新的条数,根据数据库的原子性,只有第一个抢占的进程才会返回 1,它可以进行后面的操作。而剩下返回 0 的进程,直接进入下一个等待流程即可。

这个问题我解决过,必须要答一下。
首先说一下我们的应用场景:在队列内有多个订单,有多个进程消费此队列,为了防止重复,在订单处理时加了锁,但观察日志发现,锁冲突的记录太多了,于是考虑优化。
我的解决方案从进程入手,即考虑特定进程处理某一种订单,我选用的是 记录id对进程数取模,即 第个N进程只处理 id % p_num = N 的订单
要解决此问题就要考虑进程的标识问题,PHP进程不能常驻内存,要想使得每一个PHP进程都有特定标识N,有两种方案:

  • 每个PHP脚本定义私有变量N,则需要启动N个不一样的进程,代码比较恶心,常进程处理起来不方便;

  • 定义同一类型的进程,在进程初始化时,去获取一个唯一的序号N,这个序号N的获取我这里搞了四种方案(外三内一),写了两篇博客:可以看:

从并发处理谈PHP进程间通信(一)外部介质
从并发处理谈PHP进程间通信(二)System V IPC
我这里选用的redis方案,效果不错,题主可以选一而试。

这个不应该是消息的消费么,把需要消费的数据丢到队列里面,然后多个进程进行消费。推荐:rabbitmq,redis。

当然如果说是一次性的就直接把数据丢到redis,分组,每次进程的带起消费一组数据,并且删除redis保存中的一组数据。最后全部消费完,加个特殊字符去标示全部处理完毕。

可以考虑一下把你取ID和处理的逻辑和进程分离。由单一进程负责取id,然后开子进程的方式去处理,这样你就不会取到重复的id了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题