【问题标题】:PHP/MySQL Job queueing system doing jobs more than oncePHP/MySQL 作业排队系统不止一次做作业
【发布时间】:2012-09-24 14:20:53
【问题描述】:

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

  1. Cron 将调用一个网站,该网站有一个函数,每 2 秒调用一次函数A()A() 从表 A 中搜索并检索一行
  2. 在检索一行时,A() 将使用列 working 中的值 1 更新该行
  3. A() 然后对检索到的行中的数据做一些事情
  4. A() 然后在表B 中插入一行,其值在处理步骤3 期间获得。

问题:我注意到,由于函数 A() 多次从表 A 中检索同一行,因此表 B 中有时会出现重复值。

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

请不要在没有详细说明如何实现它的情况下推荐 rabbitMQ 之类的东西。我阅读了他们的一些文档,但不明白如何实现它。谢谢!

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

【问题讨论】:

  • 如果您的第一个查询运行时间超过 2 秒会怎样?是否有任何类型的锁定来确保您不会同时运行两个查询?
  • 我的方法是......在脚本的开头,我将检查来自 cron 的前一个脚本是否仍在运行......如果正在运行,则不要运行,如果没有,则继续跨度>
  • nitpick:cron 不调用“函数”。它在指定的时间执行程序/脚本。
  • 我有一个每分钟调用一个页面(调用函数 c())的 cron 作业。这个函数 c() 执行了 30 次循环,调用函数 A(),使用 sleep() 进行延迟。
  • @Surace 来自 cron 的脚本每分钟调用此函数 30 次。所以实际上一次只运行 1 个脚本。

标签: php mysql cron rabbitmq zeromq


【解决方案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 时,为了可移植性,我更喜欢这种数据库方法而不是文件锁定机制 - 如果锁定机制在文件系统外部,则更容易在不同平台上托管您的应用程序。当然可以,而且效果很好,但根据我的个人经验,我发现这更容易使用。

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

【讨论】:

    【解决方案2】:

    一个问题可能是一开始的处理:

    Cron 将调用函数 A() 每 2 秒从表 A 中搜索和检索一行。

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

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

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

    编辑

    从你上次的更新来看,我认为是时候了:

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

    这需要很多麻烦,我认为您可能会遇到 cron 重叠的线程问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 2015-02-18
      • 2015-07-06
      • 1970-01-01
      相关资源
      最近更新 更多