PHP / MySQL作业排队系统不止一次地执行作业

时间:2012-09-24 14:20:54

标签: php mysql cron rabbitmq zeromq

我想出了一个使用PHP,MySQL和cron的非常简单的作业排队系统。

  1. Cron将调用一个网站,该网站的功能每2秒调用一次函数A()A()搜索并检索表A
  2. 中的一行
  3. 检索行后,A()将更新1
  4. 中值为working的行
  5. A()然后对检索到的行中的数据执行某些操作
  6. A()然后在表B中插入一行,其中包含处理步骤3期间获得的值。
  7. 问题:我注意到表B中有时会出现重复值,因为函数A()多次从表A检索相同的行。

    上面设计的哪一部分允许重复处理,应该如何修复?

    请不要建议像rabbitMQ这样的东西,至少要说明如何在更多细节中实现它。我阅读了他们的一些文档,但不明白如何实现它。谢谢!

    更新:我有一个cron作业,每分钟调用一个页面(调用函数c())。此函数c()执行循环30次,调用函数A(),使用sleep()延迟。

2 个答案:

答案 0 :(得分:1)

提供的答案很好,文件锁定运行良好,但是,既然你正在使用MySQL,我想我也会回答。使用MySQL,您可以使用GET_LOCKRELEASE_LOCK实现协作异步锁定。

  

*免责声明:以下示例未经测试。我之前已经成功地实现了一些非常接近的东西,下面是一般的想法。

假设您已将此GET_LOCK函数包装在名为Mutex的PHP类中:

class Mutex {
  private $_db = null;
  private $_resource = '';

  public function __construct($resource, Zend_Db_Adapter $db) {
    $this->resource = $resource;
    $this->_db      = $db;
  }

  // gets a lock for $this->_resource; you could add a $timeout value,
  // to pass as a 2nd parameter to GET_LOCK, but I'm leaving that
  // out for now
  public function getLock() {
    return (bool)$this->_db->fetchOne(
      'SELECT GET_LOCK(:resource)',
      array(
        ':resource' => $this->_resource
      ));
  }

  public function releaseLock($resource) {
    // using DO because I really don't care if this succeeds; 
    // when the PHP process terminates, the lock is released
    // so there is no worry about deadlock
    $this->_db->query(
      'DO RELEASE_LOCK(:resource)',
      array(
        ':resource' => $resource
      ));
  }
}

在A()从表中获取方法之前,让它请求锁定。您可以使用任何字符串作为资源名称。

class JobA {
  public function __construct(Zend_Db_Adapter $db) {
    $this->_db = $db;
  }

  public function A() {
    // I'm assuming A() is a class method and that the class somehow
    // acquired access to a MySQL database - pretend $this->db is a 
    // Zend_Db instance. The resource name can be an arbitrary 
    // string - I chose the class name in this case but it could be 
    // 'barglefarglenarg' or something.
    $mutex = new Mutex($this->db, get_class($this));

    // I choose to throw an exception but you could just as easily 
    // die silently and get out of the way for the next process, 
    // which often works better depending on the job
    if (!$mutex->getLock())
      throw new Exception('Unable to obtain lock.');

    // Got a lock, now select the rows you need without fear of 
    // any other process running A() getting the same rows as this
    // process - presumably you would update/flag the row so that the
    // next A() process will not select the same row when it finally
    // gets a lock. Once we have our data we release the lock

    $mutex->releaseLock();

    // Now we do whatever we need to do with the rows we selected
    // while we had the lock
  }
}

当您设计一个多个进程正在选择并修改相同数据的场景时,这种事情非常方便。使用MySQL时,我更喜欢这种数据库方法来实现文件锁定机制,以实现可移植性 - 如果锁定机制在文件系统外部,则更容易在不同平台上托管您的应用程序。当然可以做到,它工作正常,但根据我的个人经验,我发现这更容易使用。

如果您计划在数据库引擎上移植您的应用程序,那么这种方法可能不适合您。

答案 1 :(得分:0)

一个问题可能是最初的处理:

  

Cron将调用一个函数A(),该函数每隔2秒从表A中搜索并检索一行。

对于没有索引的表,处理这部分脚本可能需要两秒以上,因此您可以选择多行。

您可以使用独占文件锁来解决此问题。

我觉得不仅仅是工作流程,如果你可以显示一些附加的基本代码,也许代码中也可能存在问题。

修改

我认为根据您的上次更新来判断时间:

  

更新:我有一个调用页面的cron作业(调用函数c())   每一分钟。这个函数c()执行循环30次调用   函数A(),使用sleep()来延迟。

很多跳过篮球,我认为你可能有一个线程重叠的线程问题。