Приглашаем посетить
Ахматова (ahmatova.niv.ru)

Hack 50. Create a Message Queue

Previous
Table of Contents
Next

Hack 50. Create a Message Queue

Hack 50. Create a Message Queue Hack 50. Create a Message Queue

Use a MySQL table to create a simple message queue for delayed notifications.

Background processing is always a problem in web applications. Users like to get snappy responses from their web pages, but sometimes processing can take a while. A classic example is a web notification mail-out. The user initiates some process that requires mailing a notice to 100 people, but mailing to 100 people takes a while. Making the server wait for all the mail to go out isn't a good idea. It looks like the application is hung or about to crash. Ideally we could return a page to the user, and then in the background, send out the 100 messages. But how do we do that?

One method is to create a simple database-driven message queue. The web page puts some data into the queue, which is executed by another process later (usually fired off by the cron task-scheduling system). The message queue in this system takes two parameters: the function to be run and the arguments to the function. Therefore, you can delay almost any processing you want.

Figure 5-27 shows the simple schema for the message queue. There are really only two fields: func, which holds the name of the function, and args, which holds the XML version of the arguments.

Figure 5-27. The schema for the queue
Hack 50. Create a Message Queue


Figure 5-28 shows the status of the queue table. It starts empty, with no messages. Then a couple of messages are added. Messages are removed as they are processed.

Figure 5-28. The queue after a few operations
Hack 50. Create a Message Queue


One of the great things about a message queue is that since it's in the database, it persists even if the process that is reading or writing the queue table crashes.

5.18.1. The Code

Save the SQL in Example 5-41 as queue.sql.

Example 5-41. SQL that sets up the queue in the database
DROP TABLE IF EXISTS queue;
CREATE TABLE queue (
	id MEDIUMINT NOT NULL AUTO_INCREMENT,
	func TEXT,
	args TEXT,
	PRIMARY KEY ( id )
);

The queue itself is handled by the code in Example 5-42 (saved as queue.php).

Example 5-42. Script that handles queue management
<?php
require( "DB.php" );
$db =& DB::connect("mysql://root:password@localhost/queue", array( ));
if (PEAR::isError($db)) { die($db->getMessage( )); }
function add_to_queue( $func, $args )
{
	global $db;
	$dom = new DomDocument( );
	$root = $dom->createElement( "arguments" );
	foreach( $args as $argtext )
	{
		$arg = $dom->createElement( "argument" );
		$arg->appendChild( $dom->createTextNode( $argtext ) );
		$root->appendChild( $arg );
	}
	$dom->appendChild( $root );
	$sth = $db->prepare( "INSERT INTO queue VALUES ( 0, ?, ? )" );
	$db->execute( $sth, array( $func, $dom->saveXML( ) ) );
}
function run_queue( )
{
	global $db;
	$delsth = $db->prepare( "DELETE FROM queue WHERE id = ?" );
	$res = $db->query( "SELECT id, func, args FROM queue" );
	while( $res->fetchInto( $row ) )
	{
		$id = $row[0];
		$func = $row[1];

		$argxml = $row[2];
		$dom = new DomDocument( );
		$dom->loadXML( $argxml );
		$args = array( );
		foreach( $dom->getElementsByTagName( "argument" ) as $node )
		{
			$args []= $node->nodeValue;
		}
		call_user_func_array( $func, $args );
		$db->execute( $delsth, array( $id ) );
	}
}
?>

add.php, shown in Example 5-43, tests addition of mail to the queue.

Example 5-43. A simple script that fires off a mail notification


run.php (Example 5-44) gives a quick status notice.

Example 5-44. A script that shows what's going on (sort of) to the user
<?php
require_once( "queue.php" );
function mail_notification( $user, $text )
{
print "Mailing $user:\n$text\n\n";
}
run_queue( );
?>

The primary code in this example is in the queue.php library, used by both the add.php script, which adds elements to the queue, and the run.php script, which runs the elements currently stored in the queue.

The queue library defines two functions: add_to_queue(), which inserts a record into the queue, and run_queue(), which selects all of the rows from the table and runs them one by one. The run_queue() function is fun because it never actually knows what it's going to do! It simply reads the function name and the arguments to the function out of the database. Then it invokes the call_user_func_array() PHP function, which takes a function name as a parameter, as well as the arguments to that function as a second parameter. That means you can add functionality to the queue without ever changing these basic add and run functions.

5.18.2. Running the Hack

Running the hack starts by setting up the queue table in the database:

	% mysqladmin --user=root --password=password create queue
	% mysql --user=root --password=password queue < queue.sql

Running the add.php script using the PHP command-line interpreter adds a message to the message queue (this kind of code would run on the web page). In this case, it adds an example email item to the queue. You can run the script several times if you want to create several example email messages:

	% php add.php

Then, running the run.php script executes the messages in the event queue:

	% php run.php
	Mailing jack@oreilly.com:
	You owe us some money. Pay up.

Each message is printed to the console but can be mailed out to someone easily.

5.18.3. See Also


Previous
Table of Contents
Next