使用PHP和MySQL定时事件优先级队列

时间:2014-02-03 07:43:52

标签: php mysql priority-queue

考虑以下场景:

  • 有一个网站,用户可以在其中启动操作
  • 每个操作都有指定的持续时间,介于10到60分钟之间
  • 必须立即处理已完成的操作,因为它们会改变其他用户的视图

现在我想知道如何实现这一点 - 我显然需要某种优先级队列,我可以异步处理请求处理时间帧结束的事件。
不幸的是,我只有普通的php和mysql,因为代码是在一个虚拟主机环境中运行的,所以我不能真正使用一些花哨的背景守护进程。
我有几个想法使用mysql表作为事件队列,并在每个页面请求上轮询来自此表的事件,但这最终会遇到并发问题,除非我正确地执行,我不知道如何。

有没有一种简单的方法可以解决这个问题,我只是因为过度工程而忽视,或者我使用某种mysql机制,如果是这样,我该怎么做?

谢谢!

1 个答案:

答案 0 :(得分:0)

如果其他人遇到这个问题 - 我会在这里草拟我的解决方案。

主要思想是,我希望能够在指定时间后使用给定的参数集异步调用特定函数。

为此,我首先通过散列异步调用的所有相关信息来创建唯一标识符。此标识符用作数据库中正在运行的操作表中的主键,其中包含纯文本中的必要字段。

然后使用unix命令at对实际的异步调用进行排队,以获得精确度,并使用命令sleep进行第二次精度调度。排队命令是对当前服务器上的网页的curl调用,它将标识符作为参数并调用数据库中的函数,该函数由给定标识符引用(如果有)。

这是我使用的队列函数的摘录:

  public static function queue($name, $seconds, $function, $args = array())
  {
    $args_str = serialize($args);

    $identifier = sha512(USER::name() . "#" . SCRIPT_START . "#" . $seconds . "#" . $function . "#" . $args_str);

    $command = 'echo "sleep ' . ($seconds % 60) . '; curl http://' . $_SERVER['SERVER_NAME'] . '/async.php?identifier=' . $identifier . '" | at now+' . floor($seconds / 60) . 'minutes &>/dev/null; echo $?';
    $res = shell_exec($command);
    if ($res != 0)
      {
        // [ ... ] error handling
      }

    DB::query("
      insert into actions
          (`identifier`, `name`, `account_id`, `end_time`, `function`, `args`)
        values
          ('" . DB::escape($identifier) . "',
           '" . DB::escape($name) . "',
           '" . DB::escape(USER::id()) . "',
           '" . ceil(SCRIPT_START + $seconds) . "',
           '" . DB::escape($function) . "',
           '" . DB::escape($args_str) . "')");

    return 0;
  }

curl网页中,我从匹配给定标识符的行中检索函数和args,然后调用

call_user_func($action['function'], unserialize($action['args']));

将给定的字符串作为函数调用唤醒。

这一起允许对无状态函数的异步调用,但它要求www-data / apache用户可以访问unix at命令,但我认为这是合理的。在安全性方面,我认为如果使用得当,这种方法不会被恶意用户篡改,除非他们能以某种方式猜测哈希值。