在php和postgres中排队

时间:2014-03-31 15:00:54

标签: postgresql pdo concurrency queue

我想找到一个使用 postgres PDO (php)实现作业队列的好方法。

基本上我有一个events表,用于记录应用程序的事件,以及某种形式的预定处理器(比如说proc),它会定期检索一次事件并且执行某些例程以响应它(并根据事件的性质而自行)。

显然,只要proc的实例开始处理某个事件,我就需要将该行标记为ongoing,如下所示:

UPDATE events SET status = "ongoing" WHERE id = 3; -- >> QUERY 1 <<

精细! proc现在可以根据事件类型及其有效负载开展业务,而其他任何线程都不会处理id = 3的事件,因为它现在是ongoing

proc完成事件3时,它会将其标记为已解决&#39;所以,再一次,没有其他线程将来会照顾事件3.我们走了:

UPDATE events SET status = "resolved" WHERE id = 3; -- >> QUERY 2 <<

现在我担心这必须在交易中完成,所以我会:

BEGIN; 
-- QUERY 1
-- VARIOUS OTHER QUERIES TAKING A LOT OF TIME
-- QUERY 2
COMMIT;

据我所知,在事务内部,QUERY 1操作的更改仅在提交整个事务时对其他线程可见。这意味着虽然proc(实例1)正在进行耗时的工作(QUERY 1和QUERY 2之间的代码),但它的一些其他实例可能会读取events表,并认为没有人正在照顾事件3并继续用它来做事。很明显,这会弄乱整个事情并破坏队列的状态。

所以我的问题是:如何保留proc的交易风格,同时更改事件3的状态(来自free从交易外部可以看到ongoing立即

4 个答案:

答案 0 :(得分:1)

如上所述,另一名试图声明作业的工作人员会在查询1处阻塞。它可以看到该行的旧版本,但无法更新它 - 它会阻止。

所以不要在一次交易中做到这一点。索赔和承诺;做的工作;然后解决并提交。任何工作人员都会看到该行已被声明。此外,可以看到它已声明,这将有助于您进行调试和监控。

当你声明这一行时,你应该标记一些独特的东西(一个pid,如果只有一个工人机器,或者一个主机名和pid,如果有几个)而不是简单地使用&#39;正在进行的&#39;。这样,如果工人死亡,你可以手动清理它。

答案 1 :(得分:0)

根据定义,您无法在该交易之外看到交易中所做的更改。

  

Transactions是所有数据库系统的基本概念。事务的关键点在于它将多个步骤捆绑为单个,全有或全无操作。其他并发事务对这些步骤之间的中间状态不可见,如果发生某些阻止事务完成的故障,则根本没有任何步骤影响数据库。

对于并发问题,我建议您使用serializable transaction isolation level和/或row-level locking

答案 2 :(得分:0)

如图所示,这是不可能的。 PostgreSQL没有dirty reads,而QUERY1没有意义,因为QUERY2的效果会在被人看到之前被覆盖。

但是即使它立刻被提交并且可见(如果是独立承诺的话),这无论如何都不会令人满意。在高并发环境中,队列中SELECT行的SELECT和其{UP}状态为ongoing之间的时间足以让另一个工作者也选择它并创建您想要避免的混淆。

我认为,如果您的QUERY1替换为队列ID上的advisory lock,则可以通过替代您的设计来实现。

的伪代码:

BEGIN;
SELECT pg_try_advisory_xact_lock(3) INTO result;
IF result=true THEN
  -- grabbed the exclusive right to process this entry
  -- recheck the status now that the lock is taken
  SELECT status INTO var_status FROM events WHERE id=3;
  IF var_status='needs-to-be-done' THEN
     -- do the work...
     -- work is done
     UPDATE events SET status = 'resolved' WHERE id = 3;
  END IF;
ELSE
 -- nothing to do, another worker is on it
END IF;
COMMIT;

这种锁在交易结束时自动释放。 与SELECT后跟UPDATE相反,保证锁定以原子方式被授予或拒绝。

答案 3 :(得分:0)

Postgres 确实有一个简单的方法来实现脏读:

查询 1:读取第一条可用记录并将其标记为 ongoing

    UPDATE events SET status='ongoing', status_updated_at=clock_timestamp() WHERE id IN (
        SELECT id FROM events
        WHERE status!='ongoing'
        AND status!='resolved'
        ORDER BY id LIMIT 10
        FOR UPDATE SKIP LOCKED
    ) RETURNING *;

在不锁定这些记录或整个表的代码中处理这些事件。如果它们是事件,这里假设它们无论如何都是不可变的,所以没有人应该更新它们。

查询 2:一旦处理,将这些事件标记为 resolved

    UPDATE events SET status='resolved', status_updated_at=clock_timestamp()
      WHERE id IN (<eventIds captured from above>)

如果我在这里遗漏了什么,请告诉我?