Drupal 7 队列 API
Drupal7QueuesAPI是Drupal的一些特性,它提供了先进先出的数据结构,由Drupal本身在内部使用,并且可以完全定制。
QueuesAPI不仅仅是Drupal代码库的一部分,它在内部用作多个不同进程的一部分。BatchAPI允许一次完成很多事情,它建立在QueuesAPI之上,并提供了一些自定义的队列类。我不会在这篇文章中介绍批处理API,但我可能会在以后的文章中介绍它。
Drupal7中的新cron系统大量使用了QueuesAPI。它的工作原理是允许其他模块在正常hook_cron()调用期间创建队列项,然后再运行。每个想要在系统cron队列中包含一个项目的模块都可以通过实现一个名为的钩子来实现,该钩子hook_cron_queue_info()告诉cron在检索队列项目时回调什么函数。
例如,聚合器模块通过在cron函数调用期间创建包含名称为aggregator_feeds的提要信息的队列项,使用cron更新提要。然后从队列中检索这些项目,并通过调用该aggregator_refresh()函数并传递队列数据中包含的信息来执行这些项目。该aggregator_refresh()函数在中定义aggregator_cron_queue_info()并用于获取新的提要信息。
更新模块还利用了队列API,它用于存储请求以获取可用更新数据的任务。这些任务随后会从队列中检索并运行。
API使用面向对象的原则来生成和定义队列,因此系统是完全可定制的。
为什么要使用队列?
在深入了解如何使用和自定义队列之前,我认为有必要了解一些有关如何使用队列来完成某些任务的真实示例。
批处理
如果您正在编写的模块需要一次处理多个项目,而这些项目可能不是来自同一个函数调用,那么将它们添加到队列中可能是有意义的。然后可以在一个地方执行这些项目。
例如,假设当您插入一个节点时,您希望创建多个充当内页的其他节点。然后,您将设置一个队列来添加这些页面并添加所需的菜单项。
项目的顺序处理在
插入分类术语时,如果您想保持术语之间的父子关系,您通常希望在其他术语之前插入某些术语。通过使用队列,您可以确保一个接一个地处理所有项目,以便在创建子元素之前首先插入任何具有子元素的术语。
延迟处理
如果您有大量数据要处理,那么一次性处理可能会导致系统过载。因此,为了分散这些计算的负载,您可以将它们添加到队列中,然后缓慢地或在系统可用时处理它们。
这方面的一个例子可能是在创建工资系统或类似系统时。您需要确保每个计算都已完成,因此将要计算的项目添加到队列中意味着系统负载将保持在合理水平。这也意味着,如果系统确实出现故障,计算仍将在队列中等待处理。
导入
导入内容是许多Drupal程序员的常见任务,有时将节点所需的所有数据放在一起可能是一个昂贵的过程。通过确定需要插入哪些节点并将它们添加到队列中,您可以确保每个节点都被插入而不会使系统过载。
我遇到过一些需要插入大量节点并使用计数系统来跟踪系统在导入过程中的位置的情况。有了队列,我就不需要编写此代码,因为我可以确定添加到队列中的所有项目都会在我要求时检索到。通过这种方式,我可以作为节点的一个子部分进行处理,而不会导致系统崩溃。
防止API服务列入黑名单
许多第三方服务(如Twitter)会跟踪您执行的API请求的数量。如果您超过某个限制,他们只会阻止您再做任何事情。如果您有大量项目要处理,那么使用队列来分散这些项目以防止超出API配额可能是一个想法。
system.queue.inc文件
队列API位于文件/modules/system/system.queue.inc中。该文件包含几个接口和一些用于包装队列功能的类。
可靠与不可靠
Drupal7定义了两种类型的队列类,您可以使用它们来做不同的事情。这些是可靠的和不可靠的。请注意,该术语是不可靠的,而不是不可靠的。
可靠的
使用可靠的队列,您可以确保每个项目至少执行或处理一次。一个数据库表用于存储队列,这意味着队列可以存在于多个请求中。这也意味着如果请求失败,则队列保持不变。
Drupal知道对象可靠的唯一方法是它扩展了DrupalReliableQueueInterface接口。队列对象本身并不可靠,这意味着它是可靠的。
这是最常用的队列类型,是新队列对象的系统默认值,用途广泛。
不可靠
不可靠队列通常只保存在内存中,因此所有项目可能存在于单个请求中。不能保证队列中的所有项目都将被执行或将按顺序执行。如果请求失败,则队列可能会丢失。
这种队列的一个例子可能是将数据发送到云环境进行处理。当您将数据发送到云环境时,服务器之间通常很少或没有复杂的通信,数据只是被发送。
这种队列的一个例子可能是在创建Twitter服务机器人时。如果单个推文失败,通常对用户体验几乎没有影响,因此创建一个不可靠的队列来发送几条推文是一个理想的解决方案。
Drupal队列类
队列API中有三个类提供队列功能。它们是DrupalQueue、SystemQueue和MemoryQueue。
Drupal队列
DrupalQueue类由一个名为的静态方法组成get()。要使用它,您需要传递要检索的队列的名称,该方法将返回一个对象,您可以使用该对象与队列进行交互。以下代码是获取队列对象所需的最少代码。
$my_queue=DrupalQueue::get('my_queue');
返回的队列对象将是自定义队列对象或默认系统对象之一,具体取决于已设置的变量。您创建的任何队列都将具有一组独特的项目,这些项目将从您发布的队列名称中挂起。
您可以向该get()方法发送第二个参数,该参数是强制队列对象可靠的布尔值。这里的默认值是false,意味着该get()方法不会强制返回的队列是可靠的。
对于那些熟悉面向对象编程的人,您可以将DrupalQueue视为一个工厂类,因为它的唯一功能是创建队列对象。默认返回的对象是SystemQueue。
系统队列
SystemQueue类是Drupal中的默认队列类。它是一个可靠队列类的示例,因此实现了DrupalReliableQueueInterface接口。该类使用数据库表队列来存储和检索队列。从队列中检索的任何项目都被“租用”以确保没有两个进程获得相同的队列项目。
队列表是在Drupal安装过程中创建的,具有以下结构。
内存队列
MemoryQueue类是不可靠队列类的一个示例,因此不实现DrupalReliableQueueInterface接口。所有队列项都作为类中的$queue参数存储在内存中。这个类还实现了物品租赁,以阻止从队列中一遍又一遍地检索相同的物品。
系统变量
DrupalQueue使用三个系统变量来决定返回什么样的对象,但是设置这些变量可以改变get()方法的输出。这三个变量在此按照它们在get()方法中的使用顺序进行描述。
'queue_class_'。$name
调用该get()方法时,您可以传入要创建的队列的名称。第一个变量用于查找您创建的任何自定义类。$name参数用于构造变量名称,其值是您自定义类的名称。如果没有创建该名称的变量,则返回NULL,在这种情况下不会创建对象。
queue_default_class
如果尚未创建对象,则此变量用于查找要使用的默认系统队列类。这里的默认值是SystemQueue。
queue_default_reliable_class
如果该get()方法的第二个参数为真,则该get()方法将检查以确保当前创建的对象(如果有)实现DrupalReliableQueueInterface接口(即,它是可靠的)。如果不是,则当前创建的对象将被覆盖,以支持默认的可靠类,即SystemQueue。
该get()方法的默认行为是返回一个SystemQueue对象,但这可以通过改变这些变量来改变。
使用Drupal队列
使用队列API非常简单。您需要做的就是获取您的队列对象,然后您可以使用该对象操作您的队列数据。该createItem()方法可让您将数据添加到队列中,该numberOfItems()方法可让您查看队列中存在的项目数。以下代码获取一个队列,向其中添加一个项目,并检查项目计数。这里添加的项目是一个数组,但这只是为了证明任何东西都可以添加到队列中。MemoryQueue类将按原样存储项目,而SystemQueue类将在将项目存储到数据库之前对其进行序列化。
//创建队列对象 $queue = DrupalQueue::get('my_queue'); //创建项目 $item = array( 'dataitem1' => 'something', 'int' => 123 ); //将项目添加到队列 $queue->createItem($item); //报告存在的项目数量 echo $queue->numberOfItems(); //打印“1”"1"
要创建MemoryQueue对象,我们必须事先进行一些设置。我们可以将queue_default_class变量设置为MemoryQueue以便该get()方法将给我们一个MemoryQueue对象作为默认值。或者我们也可以设置一个名为queue_class_memory的变量,并将值设置为MemoryQueue。通过这种方式,我们可以获取MemoryQueue对象,而无需更改可能会破坏其他模块的系统设置。一旦我们有了MemoryQueue对象,它就会以非常相同的方式工作。
//设置内存队列变量 variable_set('queue_class_memory', 'MemoryQueue'); //获取MemoryQueue对象 $queue = DrupalQueue::get('memory'); //设置项目 $item = array( 'dataitem1' => 'something', 'int' => 123 ); //将项目添加到队列 $queue->createItem($item); //项目数量报告 echo $queue->numberOfItems(); //打印“1”"1"
要从队列中获取项目,我们使用claimItem()队列对象的方法。这个方法的返回值是一个包含许多属性的对象,重要的是数据属性,它包含我们放入队列的任何内容。
//获取队列 $queue = DrupalQueue::get('my_queue'); //从队列中声明项目 $got_item = $queue->claimItem(); //从项目打印数据 echo $got_item->data['dataitem1'];
如果在队列中没有找到项目,则claimItem()返回FALSE。当您在队列中查找项目时,这是一项重要的检查,因此您应该在尝试使用项目对象之前包括此检查。
//从队列中声明项目 $got_item = $queue->claimItem(); //确保我们先有一个项目 if ($got_item !== FALSE) { //使用物品 echo $got_item>data['dataitem1']; }
当使用SystemQueue类时,返回的item对象包含一个data和一个item_id属性,允许该类稍后唯一标识该项目。所以我们上面检索到的项目如下所示。MemoryQueue类返回包含更多信息的项目,例如创建时间,否则这些信息将存储在队列表中,并且通常在队列类之外不需要。它保存在MemoryQueue项目对象中,因为根本没有其他地方可以放置它。
stdClass Object ( [data] => Array ( [dataitem1] => something [qwe] => 123 ) [item_id] => 89 )
该claimItem()方法采用租用时间的可选参数。这是在物品被认领之后,在物品被放回队列之前必须经过的时间量(以秒为单位)。SystemQueue的默认时间是24小时,MemoryQueue的默认时间是30秒。您可以将自己的租用时间传递给此方法,以更改可以再次认领该项目之前经过的时间量。例如,要将租用时间设置为100秒,您可以执行以下操作。
$got_item=$queue->claimItem(100);
因此,一旦您拥有队列中的项目,您就可以对它做任何想做的事情。这仅取决于您使用队列的目的。如果您使用队列来存储将用于插入内容的ID,那么您只需将ID传递给您的代码并执行您需要执行的操作。
我认为我使用队列类的最复杂的现实世界示例是为NWDUG(西北Drupal用户组)创建事件联合器。这是几个程序员(包括我自己)之间的合作努力,我们希望使用队列将我们的聚会联合到不同的活动网站。我们存储在队列中的唯一数据是节点ID和服务。这意味着我们可以加载最新版本的事件节点并将其传递给我们为该服务创建的发布事件挂钩。这是代码的那部分。
//声明事件项 $event = $queue->claimItem(); if ($event !== FALSE) { //如果我们有一个事件,则在子模块中调用publishevent钩子 $result = module_invoke($event->data['service'], 'publishevent', node_load($event->data['nid'])); }
释放和删除项目
一旦我们有了我们的队列项并使用了它,我们就可以释放它(重置租用时间)或删除它。要将项目释放回队列,请使用releaseItem()方法,将项目对象作为单个参数传递。
$queue->releaseItem($got_item);要从队列中完全删除项目,请使用deleteItem()方法,将项目对象作为单个参数传递。$queue->deleteItem($got_item);
定制
如果默认系统类不能完全满足您的需要,您可以创建自己的队列类,它可以完全改变QueuesAPI的工作方式。创建您自己的队列类时,最好扩展其中一个默认类或实现DrupalQueueInterface。这将为您提供其他模块开发人员可以使用的通用接口。
如果你的队列类是可靠的,那么它必须实现DrupalReliableQueueInterface,尽管你也可以扩展SystemQueue,这几乎是一样的。get()DrupalQueue的方法将检查以确保您的对象在创建可靠队列时实现DrupalReliableQueueInterface,因此如果您的类不这样做,您将获得SystemQueue类。
在您构建了自己的自定义队列类之后,您只需创建一个名为'queue_class_'的变量即可使其工作。$name,其值是您的队列类的类名。然后,您可以get()使用变量$name作为字符串调用DrupalQueue::。设置这个变量通常会在安装钩子或类似的东西中完成。例如,假设我的类名为MyCustomQueueClass,那么我将使用以下代码将其作为队列对象获取。
//设置变量(通常在安装钩子中完成) variable_set('queue_class_mycustom', 'MyCustomQueueClass'); //获取队列 object $queue = DrupalQueue::get('mycustom');
在我们检索到队列对象后,我们可以像以前一样与它进行交互,实现DrupalQueueInterface为我们提供了相同的方法来使用。
自定义队列类示例
我认为创建一些自定义队列类来做不同的事情来演示创建新的队列功能是多么容易,这可能是一个想法,所以他们在这里。
堆栈类
在创建我们自己的队列类时,我们可以做的最简单的事情是用我们自己的功能覆盖默认的队列类。Stack类是一个栈数据结构的实现,后进先出的实现,使用SystemQueue类作为模板。我们从SystemQueue类更改的唯一内容是在第一个SQL查询中,我们将其更改为按创建降序而不是升序排序。这意味着我们放入对象的最后一个项目是我们返回的第一个项目。
class Stack extends SystemQueue { public function claimItem($lease_time = 30) { while (TRUE) { $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name ORDER BY created DESC', 0, 1, array(':name' => $this->name))->fetchObject(); if ($item) { $update = db_update('queue') ->fields(array( 'expire' => time() + $lease_time, )) ->condition('item_id', $item->item_id) ->condition('expire', 0); //如果有受影响的行,则此更新成功。 if ($update->execute()) { $item->data = unserialize($item->data); return $item; } } else { //目前没有可领取的物品。 return FALSE; } } } }
WatchdogSystemQueue
此类扩展了SystemQueue类,并在每次完成任何操作时创建日志。它还保留了原始的SystemQueue功能,因此可能是了解Drupal系统内部发生的事情的好方法。您可以以正常方式使用此类,也可以覆盖queue_default_class和queue_default_reliable_class变量,以便您的Drupal安装使用此类作为默认值。
class WatchdogSystemQueue extends SystemQueue { public function __construct($name) { watchdog('queue', '%name queue created', array('%name' => $name)); parent::__construct($name); } public function createItem($data) { watchdog('queue', 'Item created : %data', array('%data' => print_r($data, TRUE))); parent::createItem($data); } public function numberOfItems() { $count = parent::numberOfItems(); watchdog('queue', 'Current item count = %num', array('%num' => $count)); } public function claimItem($lease_time = 3600) { $return_value = parent::claimItem($lease_time); watchdog('queue', 'Item claimed %item (lease time = %lease)', array('%item' => print_r($return_value, TRUE), '%lease' => $lease_time)); return $return_value; } public function deleteItem($item) { watchdog('queue', 'Item deleted : %item', array('%item' => print_r($item, TRUE))); parent::deleteItem($item); } public function releaseItem($item) { watchdog('queue', 'Item released : %item', array('%item' => print_r($item, TRUE))); parent::releaseItem($item); } public function createQueue() { watchdog('queue', 'Queue created '); parent::createQueue(); } public function deleteQueue() { watchdog('queue', 'Queue deleted'); parent::deleteQueue(); } }
EventQueue类
该类是NWDUG网站的一部分所必需的。我们需要的是在发布一个事件之后,在该事件被联合到不同的服务(如Twitter、EventBrite、YahooUpcoming)之前的一段宽限期。实施的是延迟队列,其中任何添加的项目仅在45分钟后可用。我们创建的类扩展了SystemQueue类,只是以与上面Stack类相同的方式覆盖了一些核心功能。
class EventQueue extends SystemQueue { public function claimItem($lease_time = 30) { while (TRUE) { //防止检索不到45分钟(2700秒)的项目 $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE expire = 0 AND name = :name AND created >= UNIX_TIMESTAMP(DATE_ADD(NOW(), INTERVAL 2700 SECOND)) ORDER BY created ASC', 0, 1, array(':name' => $this->name))->fetchObject(); if ($item) { $update = db_update('queue') ->fields(array( 'expire' => time() + $lease_time, )) ->condition('item_id', $item->item_id) ->condition('expire', 0); //如果有受影响的行,则此更新成功。 if ($update->execute()) { $item->data = unserialize($item->data); return $item; } } else { //目前没有可领取的物品。 return FALSE; } } } }
RandomMemoryQueue
RandomMemoryQueue是一个有点愚蠢的类,但创建它是为了表明可以以与SystemQueue类相同的方式扩展MemoryQueue类。项目按顺序添加但以随机顺序检索,保持原始类的租用时间系统。
class RandomMemoryQueue extends MemoryQueue { public function claimItem($lease_time = 30) { $available_items = array(); //提取挖掘可用项目 foreach ($this->queue as $key => $item) { if ($item->expire == 0) { $available_items[] = $item; } } //随机选择一个(如果有) if (count($available_items) > 0) { $queue_length = count($this->queue); $rand_item = rand(0, $queue_length - 1); $item = $available_items[$rand_item]; $item->expire = time() + $lease_time; return $item; } return FALSE; } }
创造与毁灭
DrupalQueueInterface接口有两种方法可用于创建和销毁队列。安装模块时已完成,只需运行一次。您可以通过使用DrupalQueue::get()方法以与通常相同的方式获取队列对象来调用它们。
createQueue()
用于创建任何自定义队列表或设置变量。应该在安装钩子内调用。
deleteQueue()
用于删除任何自定义队列表并删除任何自定义变量。应在卸载挂钩内调用。
任何核心Drupal队列类都不使用这些方法,因为在安装Drupal时已经为它们设置了一切。
提示
以下是使用DrupalQueuesAPI时可能会派上用场的一些技巧。
除非你真的需要重写整个类,否则最好扩展SystemQueue或MemoryQueue。
对于开源项目中的自定义队列类,尽量保持与默认系统队列相同的检索项目结构。
不要更改系统变量来获取MemoryQueue对象,而是使用queue_class_
默认队列类从不检查队列项的唯一性。如果您不小心,完全有可能一遍又一遍地添加相同的项目。
资源
Drupal队列API
该API在http://api.drupal.org/api/drupal/modules--system--system.queue.inc/group/queue/7中有很好的记录
队列UI模块
已经创建了一个名为QueueUI的模块,它允许您检查队列的内容。
源代码
队列API的实际源代码有很好的文档记录,可以在/modules/system/system.queue.inc找到