我需要cron任务来处理队列吗?


32

我有一项任务大约需要45分钟才能完成,并且每天都需要完成(将用户同步到多个外部数据库等)。

为了处理工作,我设置了一个cron队列,hook_cron_queue_info()如下所示:

function mymodule_cron_queue_info() {
  $queues = array();
  $queues['update_users_queue'] = array(
    'worker callback' => '_mymodule_process_user_queue_item',
    'time' => 120,
  );
  return $queues;
}

我使用此功能填充队列:

function mymodule_queue_all_users_for_synching() {
  //...query for users...

  $queue = DrupalQueue::get('update_users_queue');
  foreach($users as $user) {
    $queue->createItem($user);
  }
}

队列填充功能被称为cron任务。我使用Elysia Cron,因此我的实现hook_cronapi()是:

function mymodule_cronapi($op, $job = NULL) {
  $items = array();
  $items['queue_users_for_synch'] = array(
    'description' => 'Queue all user accounts for synching.',
    'rule' => '0 3 * * *', // Run this job every day at 3am.
    'callback' => 'mymodule_queue_all_users_for_synching',
  );
  return $items;
}

每个队列项的辅助函数,定义mymodule_cron_queue_info如下:

function _mymodule_process_user_queue_item($item) {
  //...synchronize user ($item)...
}

我的问题是,cron何时真正开始处理队列?

假设我每天凌晨3点填满队列,并希望每30分钟处理120秒直到完成-我是否需要创建另一个cron任务?


我要提到我使用Drupal的7
joe_flash

1
我对这个问题也很好奇。想听到是或否。我们在一个D7项目中大量使用了队列API。从视觉上看,当cron运行时,{queue}表行已清除。因此,假设它是肯定的。
Sivaji

Answers:


19

当Drupal运行cron任务时,它将自动处理从模块中定义的任何cron队列drupal_cron_run();首先hook_cron()调用实现,然后清空cron队列。

添加hook_cronapi(),您可以为处理模块的cron队列的另一个函数添加一个条目。

function mymodule_cronapi($op, $job = NULL) {
  $items = array();

  $items['queue_users_for_synch'] = array(
    'description' => 'Queue all user accounts for synching.',
    'rule' => '0 3 * * *', // Run this job every day at 3am.
    'callback' => 'mymodule_queue_all_users_for_synching',
  );

  $items['clean_queue'] = array(
    'description' => 'Clean the queue for the user synching.',
    'rule' => '0 4 * * *', // Run this job every day at 4 AM.
    'callback' => 'mymodule_clean_queue',
  );

  return $items;
}

function mymodule_clean_queue() {
  $queues = module_invoke('mymodule', 'cron_queue_info');
  drupal_alter('cron_queue_info', $queues);

  // Make sure every queue exists. There is no harm in trying to recreate an
  // existing queue.
  foreach ($queues as $queue_name => $info) {
    DrupalQueue::get($queue_name)->createQueue();
  }

  foreach ($queues as $queue_name => $info) {
    $function = $info['worker callback'];
    $end = time() + (isset($info['time']) ? $info['time'] : 15);
    $queue = DrupalQueue::get($queue_name);
    while (time() < $end && ($item = $queue->claimItem())) {
      $function($item->data);
      $queue->deleteItem($item);
    }
  }
}

另一种选择是让Drupal为您处理cron队列,但这会在执行Drupal cron任务时发生。如果要更频繁地清空模块的cron队列,则只能添加由Elysia Cron模块处理的新cron任务。

Elysia Cron模块处理in中的cron队列elysia_cron_run();此函数是从elysia_cron_cron()(的实现hook_cron()),drush_elysia_cron_run_wrapper()(Drush命令回调)和自己的cron.php调用的。如果您按照INSTALL.txt文件中的说明进行操作(尤其是在“ STEP B:更改系统目录(可选)”中),并且用http:// example替换了对http://example.com/cron.php的任何调用.com / sites / all / modules / elysia_cron / cron.php,Elysia Cron模块应该已经在处理cron队列。如果确实需要,我建议的代码可用于加快处理模块中使用的cron队列的速度。

// This code is part of the code executed from modules/elysia_cron/cron.php.
define('DRUPAL_ROOT', getcwd());

include_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_override_server_variables(array(
  'SCRIPT_NAME' => '/cron.php',
));
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

if (!isset($_GET['cron_key']) || variable_get('cron_key', 'drupal') != $_GET['cron_key']) {
  watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE);
  drupal_access_denied();
}
elseif (variable_get('maintenance_mode', 0)) {
  watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE);
  drupal_access_denied();
}
else {
  if (function_exists('elysia_cron_run')) {
    elysia_cron_run();
  }
  else {
    drupal_cron_run();
  }
}

啊,谢谢@kiam!这就是我所怀疑的,但是我无法完全绕开它。
joe_flash

实际上,我认为我在这里丢失了一些东西。您说替代方法是让Drupal为我处理cron队列;我想我最初的问题的一部分是什么时候真正发生?每次crontab请求cron.php吗?如果是这样,那每分钟都会发生一次(请参阅我对@David的回复的第一条评论)。
joe_flash

1
值得一提的是,Elysia cron似乎拥有自己的cron_queue处理器实现,elysia_cron_run每次请求Elysia的cron.php时都会自动处理cron队列。
大卫·托马斯

@joe_flash我很抱歉:我应该更清楚一些。是的,在调用cron.php时,Drupal会运行cron任务(适用于Drupal 7之前的每个Drupal版本)。在Drupal 8中,不再存在cron.php,并且使用不同的系统路径执行cron任务。
kiamlaluno

2

队列将在设置的时间通过Elysia cronapi钩子填充。

但是,只要发生标准Drupal cron运行,就将处理该队列。

请参阅核心末尾的此工作程序回调处理代码段:drupal_cron_run

 foreach ($queues as $queue_name => $info) {
    $function = $info['worker callback'];
    $end = time() + (isset($info['time']) ? $info['time'] : 15);
    $queue = DrupalQueue::get($queue_name);
    while (time() < $end && ($item = $queue->claimItem())) {
      $function($item->data);
      $queue->deleteItem($item);
    }
  }

David,也许Elysia在这里引入了一些麻烦?我已将crontab设置为每分钟触发一次 Elysia cron.php脚本,这使Elysia可以按分钟的分辨率控制任务时间。但是实际上并没有每分钟运行任何任务-是什么让我认为我需要创建一个专门用于队列的任务?
joe_flash

只要drupal_cron_run被调用,@ joe_flash 就会处理您的cron队列工作程序回调。
大卫·托马斯

啊,我认为你是对的。但是,drupal_cron_run不会从Elysia cron.php脚本调用(启用Elysia后);elysia_cron_run改为使用。
joe_flash

在这种情况下,hook_cron_queue_info除非您按照drupal_cron_run上述核心功能代码段指定了自己的辅助回调,否则您似乎无法使用Elysia cron 。
大卫·托马斯

elysia_cron_run不会打电话drupal_cron_run,但是打电话,module_invoke_all('cron_queue_info')并且会做一些花哨的裤子进行多通道处理,使烟雾冒出来。
joe_flash

1

如上所述,使用Elysia Cron时,不会处理您的队列。

您(和drupal)无权访问否则将在drupal_run_cron上运行的队列

解决方法是创建一个自定义cron任务-(这对elysia cron来说是可见的)以处理所有队列或您想要的一个队列并在那里调用队列处理。即:

function mymodule_cron() {
// below is copied from drupal_cron_run() in common.inc

// Grab the defined cron queues.
$queues = module_invoke_all('cron_queue_info');
drupal_alter('cron_queue_info', $queues);

//if you want to target only one queue you can remove 'foreach'
and your $info = $queues['your_queue'];

  foreach ($queues as $queue_name => $info) {
    if (!empty($info['skip on cron'])) {
      // Do not run if queue wants to skip.
      continue;
    }
    $callback = $info['worker callback'];
    $end = time() + (isset($info['time']) ? $info['time'] : 15);
    $queue = DrupalQueue::get($queue_name);
     while (time() < $end && ($item = $queue->claimItem())) {
      try {
        call_user_func($callback, $item->data);
        $queue->deleteItem($item);
      }
      catch (Exception $e) {
        // In case of exception log it and leave the item in the queue
        // to be processed again later.
        watchdog_exception('cron', $e);
      }
    }
  }
}

现在队列处理可以由ElysiaCron控制


0

我不使用Elysia,但我的解决方案始终是这样的:

function mymodule_cron() {
  $queue = DrupalQueue::get('mymoudule_queue');
  $queue->createQueue();
  $item = $queue->claimItem(300);

  if (!empty($item->data)) {

    // Do the work.

    if ($sucess) {
      $queue->deleteItem($item);
      watchdog('mymodule', 'It worked.');
    }
    else {
      watchdog('mymodule', 'It did not work!', array(), WATCHDOG_ALERT);
    }
  }
}

对于每次cron运行,它仅处理一项。也许您想更改它。


0

我也一直在努力解决这个问题,因为我是第一次与Elysia cron一起使用Queue API。仔细检查后,您可以看到在调用elysia_cron_run函数时,Elysia cron执行队列项目。请参阅elysia_cron.module文件中第1044行的代码段:

if (EC_DRUPAL_VERSION >= 7) {
  // D7 Queue processing
  foreach ($queues as $queue_name => $info) {
    $function = $info['worker callback'];
    $end = time() + (isset($info['time']) ? $info['time'] : 15);
    $queue = DrupalQueue::get($queue_name);
    while (time() < $end && ($item = $queue->claimItem())) {
      $function($item->data);
      $queue->deleteItem($item);
    }
  }
}

当使用Elysia cron时,这有助于我消除队列处理的神秘性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.