Commit 157a03c4 authored by robertDouglass's avatar robertDouglass

Initial commit of an API for using memcache with Drupal. Not ready for production.

parents
To install, move memcache.inc to your DRUPAL/includes directory (where the other *.inc files live).
Then patch bootstrap.inc using the bootstrap.patch file.
In your settings.php file you need to map out your memcache servers. Here is an example:
Example 1.
$conf = array(
'memcache_servers' => array('default' => array('localhost' => array(11211)),
),
);
Example 1 will map a server at localhost:11211 which will be used as the default. It is good to
always specify a default and you may consider making it the biggest memory allocation.
Example 1.
$conf = array(
'memcache_servers' => array('default' => array('12.34.56.78' => array(11211, 11212, 11213)),
'cache' => array('98.76.54.32' => array(33433)),
),
);
Example 2 maps three allocations for 'default' at 12.34.56.78:11211, 12.34.56.78:11212, and
12.34.56.78:11213. These should act as a pool, meaning Memcache's failover should utilize all
three for the 'default' bin.
WARNING: Local tests show that failover is not working yet!
Then, a server is allocated to the 'cache' bin (98.76.54.32:33433). This means that anything get or set with the bin 'cache' will go there instead of to the other (default).
\ No newline at end of file
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.144
diff -u -F^f -r1.144 bootstrap.inc
--- includes/bootstrap.inc 2 Jan 2007 10:21:39 -0000 1.144
+++ includes/bootstrap.inc 11 Jan 2007 18:36:48 -0000
@@ -806,6 +806,7 @@ function _drupal_bootstrap($phase) {
break;
case DRUPAL_BOOTSTRAP_SESSION:
+ require_once './includes/memcache.inc';
require_once './includes/session.inc';
session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
session_start();
@@ -875,6 +876,7 @@ function _drupal_cache_init($phase) {
*/
function drupal_maintenance_theme() {
global $theme;
+ require_once './includes/memcache.inc';
require_once './includes/path.inc';
require_once './includes/theme.inc';
require_once './includes/common.inc';
<?php
// $ID$
/*
* A memcache API for Drupal.
*/
/**
* Place an item into memcache
*
* @param $key The string with with you will retrieve this item later.
* @param $value The item to be stored.
* @param $exp Parameter expire is expiration time in seconds. If it's 0, the item never expires
* (but memcached server doesn't guarantee this item to be stored all the time, it could be
* deleted from the cache to make place for other items).
* @param $bin The name of the Drupal subsystem that is making this call. Examples could be
* 'cache', 'alias', 'taxonomy term' etc. It is possible to map different $bin values to
* different memcache servers.
*
* @return bool
*/
function dmemcache_set($key, $value, $exp = 0, $bin = 'default') {
if ($mc = dmemcache_object($bin)) {
$full_key = dmemcache_key($key, $bin);
if (!$mc->set($full_key, $value, FALSE, $exp)) {
watchdog('memcache', 'Failed to set key: ' . $full_key);
}
else {
return TRUE;
}
}
return FALSE;
}
/**
* Retrieve a value from the cache.
*
* @param $key The key with which the item was stored.
* @param $bin The bin in which the item was stored.
*
* @return The item which was originally saved or FALSE
*/
function dmemcache_get($key, $bin = 'default') {
if ($mc = dmemcache_object($bin)) {
$full_key = dmemcache_key($key, $bin);
return $mc->get($full_key);
}
}
/**
* Deletes an item from the cache.
*
* @param $key The key with which the item was stored.
* @param $bin The bin in which the item was stored.
*
* @return Returns TRUE on success or FALSE on failure.
*/
function dmemcache_delete($key, $bin = 'default') {
if ($mc = dmemcache_object($bin)) {
$full_key = dmemcache_key($key, $bin);
return $mc->delete($full_key);
}
return FALSE;
}
/**
* Immediately invalidates all existing items. dmemcache_flush doesn't actually free any
* resources, it only marks all the items as expired, so occupied memory will be overwritten by
* new items.
*
* @param $bin The bin to flush. Note that this will flush all bins mapped to the same server
* as $bin. There is no way at this time to empty just one bin.
*
* @return Returns TRUE on success or FALSE on failure.
*/
function dmemcache_flush($bin = 'default') {
if ($mc = dmemcache_object($bin)) {
return $mc->flush();
}
}
function dmemcache_stats($bin = 'default') {
if ($mc = dmemcache_object($bin)) {
return $mc->getStats();
}
}
/**
* Returns an Memcache object based on the bin requested. Note that there is nothing preventing
* developers from calling this function directly to get the Memcache object. Do this if you need
* functionality not provided by this API or if you need to use existing code. Otherwise, use
* the dmemcache API functions provided here.
*
* @param $bin The bin which is to be used. Note that this maps to a physical server that
* may or may not be shared with other bins.
* @param $flush Rebuild the bin/server mapping from the global $conf array.
*
* @return an Memcache object or FALSE.
*/
function dmemcache_object($bin = 'default', $flush = FALSE) {
global $conf;
static $caches;
if ($flush) {
unset($caches);
}
if (!isset($caches)) {
// initialize the static cache
$caches = array();
if (isset($conf['memcache_servers'])) {
foreach ($conf['memcache_servers'] as $bin_name => $hosts) {
if (!isset($caches[$bin_name])) {
$caches[$bin_name] = new Memcache;
}
foreach ($hosts as $host => $ports) {
foreach ($ports as $port) {
// NOTE: We add the server with the persistent flag set to TRUE.
// According to the documentation, this will have no effect if the memcache extension
// is loaded dynamically. If your application is generating too many memcache
// connections, you need to compile PHP with the memcache extension.
$caches[$bin_name]->addServer($host, $port);
}
}
}
}
// If $conf['memcache_servers'] was not set, we try to initialize the default server
else {
$mc = new Memcache;
$mc->connect('localhost', '11211');
if ($mc->getServerStatus('localhost', 11211)) {
$caches['default'] = $mc;
}
}
}
// If there is a server set up for the requested $bin, return it.
if (isset($caches[$bin])) {
return $caches[$bin];
}
// Otherwise, return the first server listed.
else {
$tmp = $caches;
return array_shift($tmp);
}
return FALSE;
}
/**
* Build a key that is urlencoded (better whitespace handling) and namespaced with the bin.
*
* @param $key The base key that identifies an object.
* @param $bin The bin in which the object resides or will reside.
*
* @return A string that will ultimately be used as the actual key.
*/
function dmemcache_key($key, $bin = 'default') {
return urlencode($bin) . '::' . urlencode($key);
}
\ No newline at end of file
<?php
// $ID$
/*
* Some unit tests used while developing. You may find them instructional.
* Place the memcachetests.php file in the root Drupal directory. Start memcache servers
* on localhost:11211 and localhost:11212
*/
include_once './includes/memcache.inc';
print "<h1>Memcache configuration details</h1>";
print "<ul><li>memcache.allow_failover=". ini_get('memcache.allow_failover'). "</li>";
print "<li>memcache.max_failover_attempts=". ini_get('memcache.max_failover_attempts'). "</li>";
print "<li>memcache.chunk_size=". ini_get('memcache.chunk_size'). "</li>";
print "<li>memcache.default_port=". ini_get('memcache.default_port'). "</li>";
print "</ul>";
global $conf;
$conf = array(
'memcache_servers' => array('default' => array('localhost' => array(11211)),
),
);
##############
// Begin Tests
##############
// Test 1. Connect to server and retrieve stats
###############################################
printHeader();
$mc = dmemcache_object();
formatStats($mc->getStats());
// Test 2. Set a number of keys and retrieve their values
#########################################################
printHeader();
$keys = array('a', time(), 'http://www.robshouse.net/home/page?q=xyz&p=x', 'What about space?');
print '<ol>';
foreach ($keys as $key) {
testKeyValue($mc, $key, 'Hi Robert');
}
print '</ol>';
// Test 3. Set a number of PROBLEMATIC keys and retrieve their values
#####################################################################
printHeader();
print '<ol>';
$key = ' ';
testKeyValue($mc, $key, 'Hi Dude');
$key = "\n";
testKeyValue($mc, $key, 'Hi Dude 2');
print '<li><em>space and line break different?</em>='. $mc->get(' ').'</li>';
print '</ol>';
// Test 4. Test flushing the $mc object from dmemcache_object
#############################################################
printHeader();
formatStats($mc->getStats());
$conf = array(
'memcache_servers' => array('default' => array('localhost' => array(11212)),
),
);
$mc = dmemcache_object('default', TRUE);
formatStats($mc->getStats());
// Test 5. Confirm that space and line break are treated as the same character
##############################################################################
printHeader();
$mc->set("\n", "This is a new line", FALSE, 5);
print '<ol>';
print '<li>'. $mc->get("\n"). '</li>';
print '<li>'. $mc->get(" "). '</li>';
print '</ol>';
// Test 6. Try out dmemcache_set and dmemcache_get
##################################################
printHeader();
$keys = array('a', time(), 'http://www.robshouse.net/home/page?q=xyz&p=x', 'What about space?');
print '<ol>';
foreach ($keys as $key) {
dtestKeyValue($key, 'Hi Robert 2');
}
print '</ol>';
// Test 7. See if newline and space are identical using dmemcache
// conclusion: the urlencode() fixes this problem.
#################################################################
printHeader();
dmemcache_set("\n", "This is a new line");
print '<ol>';
print '<li>'. dmemcache_get("\n"). '</li>';
print '<li>'. dmemcache_get(" "). '</li>';
print '</ol>';
// Test 8. See if addServer actually pools the server resources
###############################################################
printHeader();
// Set up $conf so that both available servers map to default
$GLOBALS['conf']['memcache_servers'] = array('default' => array('localhost' => array(11211, 11212)),
);
// clear the $mc object
dmemcache_object('default', TRUE);
// make independent connections so we can display stats
$mc1 = new Memcache;
$mc1->connect('localhost', 11211);
$mc2 = new Memcache;
$mc2->connect('localhost', 11212);
// helper function specific to this test
function formatStats2($mc1, $mc2, $flush = FALSE) {
static $count, $first;
$stats1 = $mc1->getStats();
$stats2 = $mc2->getStats();
if ($flush) {
unset($first);
}
if (!isset($first)) {
$count = 1;
$first = FALSE;
print "<table border='1'><tr><th> </th><th>Server 1</th><th>Server 2</th></tr>";
}
print "<tr><td>$count</td><td>". $stats1['bytes']. "</td><td>". $stats2['bytes']. "</td></tr>";
$count++;
}
$last_key = $last_value = '';
$time = time();
for ($i = 1; $i < 10001; $i++) {
$last_key = $time.$i. 'key';
$last_value = 'Some very random thoughts about things in general'. $time;
dmemcache_set($last_key, $last_value, FALSE, 0);
if ($i % 1000 == 1) {
formatStats2($mc1, $mc2);
flush();
}
}
print "</table>";
// Test 9. Try using flush to clear servers
// Conclusion: It probably works, except that it doesn't actuall clear the memory.
// It only sets it as invalid so that it gets overwritten.
###########################################
printHeader();
formatStats2($mc1, $mc2, TRUE);
print "</table>";
dmemcache_flush();
formatStats2($mc1, $mc2, TRUE);
print "</table>";
// Test 10. See what the extended stats offer
#############################################
printHeader();
print "<pre>";
$types = array('reset', 'malloc', 'maps', 'slabs', 'items', 'sizes');
foreach ($types as $type) {
print "<h2>$type</h2>";
print "<h3>Server 1</h3>";
print_r($mc1->getExtendedStats($type));
print "<h3>Server 2</h3>";
print_r($mc2->getExtendedStats($type));
}
print "</pre>";
// Test 11. Test delete.
########################
printHeader();
dmemcache_set('delete me', 'Goodbye world');
print "<h2>". dmemcache_get('delete me'). "</h2>";
dmemcache_delete('delete me');
print "<h2>Nothing here ---->". dmemcache_get('delete me'). "<--</h2>";
###################
// Helper functions
###################
function testKeyValue($mc, $key, $value) {
$mc->set($key, $value, FALSE, 5);
printKeyValue($key, $mc->get($key));
}
function dtestKeyValue($key, $value, $bin = 'default') {
dmemcache_set($key, $value, 5, $bin);
printKeyValue($key, dmemcache_get($key));
}
function printKeyValue($key, $value) {
print '<li>'. $key. '='. $value. '</li>';
}
function formatStats($stats = array()) {
print '<ul>';
foreach ($stats as $name => $value) {
print '<li>'. $name. '='. $value. '</li>';
}
print '</ul>';
}
function printHeader() {
static $count = 1;
print "<a name='$count'><h2>Test ". $count++. "</h2></a>";
}
?>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment