dmemcache.inc 10.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<?php
// $Id$

/*
 * Core dmemcache functions required by:
 *   memcache.inc
 *   memcache.db.inc
 *   session-memcache.inc
 *   session-memcache.db.inc
 */

global $_memcache_statistics;
$_memcache_statistics = array('get' => array(), 'set' => array(), 'hit' => array());

/*
 * A memcache API for Drupal.
 */

/**
 *  Place an item into memcache
 *
22
 * @param $key The string with which you will retrieve this item later.
23
 * @param $value The item to be stored.
24 25 26 27 28 29 30 31 32
 * @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.
 * @param $mc Optionally pass in the memcache object.  Normally this value is
 *   determined automatically based on the bin the object is being stored to.
33 34 35
 *
 * @return bool
 */
36
function dmemcache_set($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) {
37 38 39
  global $_memcache_statistics;
  $_memcache_statistics['set'][] = $key;
  $_memcache_statistics['bins'][] = $bin;
40
  if ($mc || ($mc = dmemcache_object($bin))) {
41
    $full_key = dmemcache_key($key, $bin);
42 43
    if (class_exists('Memcached')) {
      return $mc->set($full_key, $value, $exp);
44 45
    }
    else {
46
      return $mc->set($full_key, $value, MEMCACHE_COMPRESSED, $exp);
47 48 49 50 51
    }
  }
  return FALSE;
}

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
/**
 *  Add an item into memcache
 *
 * @param $key The string with which 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.
 * @param $mc Optionally pass in the memcache object.  Normally this value is
 *   determined automatically based on the bin the object is being stored to.
 * @param $flag If using the older memcache PECL extension as opposed to the
 *   newer memcached PECL extension, the MEMCACHE_COMPRESSED flag can be set
 *   to use zlib to store a compressed copy of the item.  This flag option is
 *   completely ignored when using the newer memcached PECL extension.
 *
 * @return bool
 */
73 74 75 76 77 78 79
function dmemcache_add($key, $value, $exp = 0, $bin = 'cache', $mc = NULL, $flag = FALSE) {
  global $_memcache_statistics;
  $_memcache_statistics['add'][] = $key;
  $_memcache_statistics['bins'][] = $bin;
  if ($mc || ($mc = dmemcache_object($bin))) {
    $full_key = dmemcache_key($key, $bin);
    if (class_exists('Memcached')) {
Jeremy's avatar
Jeremy committed
80
      return $mc->add($full_key, $value, $exp);
81 82
    }
    else {
Jeremy's avatar
Jeremy committed
83
      return $mc->add($full_key, $value, $flag, $exp);
84 85
    }
  }
86
  return FALSE;
87 88
}

89 90 91 92 93 94 95 96
/**
 * 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
 */
97
function dmemcache_get($key, $bin = 'cache', $mc = NULL) {
98 99 100
  global $_memcache_statistics;
  $_memcache_statistics['get'][] = $key;
  $_memcache_statistics['bins'][] = $bin;
101
  if ($mc || ($mc = dmemcache_object($bin))) {
102
    $full_key = dmemcache_key($key, $bin);
103
    $result = $mc->get($full_key);
104
    if ($result) {
105 106 107 108 109 110 111 112 113 114 115 116 117
      $_memcache_statistics['hit'][] = $key;
      return $result;
    }
  }
}

/**
 * Retrieve multiple values from the cache.
 *
 * @param $keys The keys with which the items were stored.
 * @param $bin The bin in which the item was stored.
 *
 * @return The item which was originally saved or FALSE
118 119
 *
 * TODO: Record statistics w/ multi-get
120 121 122 123 124 125 126
 */
function dmemcache_get_multi($keys, $bin = 'cache', $mc = NULL) {
  global $_memcache_statistics;
  $_memcache_statistics['get'] = array_merge($_memcache_statistics['get'], $keys);
  $_memcache_statistics['bins'][] = $bin;
  $results = array();
  if ($mc || ($mc = dmemcache_object($bin))) {
127 128 129 130
    $full_keys = array();
    foreach ($keys as $key => $cid) {
      $full_keys[] = dmemcache_key($cid, $bin);
    }
131 132 133 134
    if (class_exists('Memcached')) {
      $results = $mc->getMulti($full_keys);
    }
    else {
135
      $results = $mc->get($full_keys);
136 137
    }
  }
138
  return $results;
139 140 141 142 143 144 145 146 147 148
}

/**
 * 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.
 */
149 150
function dmemcache_delete($key, $bin = 'cache', $mc = NULL) {
  if ($mc || ($mc = dmemcache_object($bin))) {
151
    $full_key = dmemcache_key($key, $bin);
152
    return $mc->delete($full_key);
153 154 155 156 157 158 159 160 161 162 163 164 165 166
  }
  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.
 */
167 168
function dmemcache_flush($bin = 'cache', $mc = NULL) {
  if ($mc || ($mc = dmemcache_object($bin))) {
169
    return memcache_flush($mc);
170 171 172 173
  }
}

function dmemcache_stats($bin = 'cache', $type = '') {
174 175 176 177 178 179 180 181
  // resolve requests for 'default' type to ''
  if ($type == 'default') {
    $type = '';
  }
  // resolve requests for 'default' bin to 'cache'.
  if ($bin == 'default') {
    $bin = 'cache';
  }
182
  if ($mc = dmemcache_object($bin)) {
183 184 185
    if (class_exists('Memcached')) {
      return $mc->getStats();
    }
186 187
    // The PHP Memcache extension 3.x version throws an error if the stats
    // type is NULL or not in {reset, malloc, slabs, cachedump, items, sizes}.
188
    // If $type is 'default', then no parameter should be passed to the
189 190
    // Memcache memcache_get_extended_stats() function.
    if ($type == 'default' || $type == '') {
191
      return memcache_getExtendedStats($mc);
192 193
    }
    else {
194
      return memcache_getExtendedStats($mc, $type);
195
    }
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
  }
}

/**
 * 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 legacy code. Otherwise, use the dmemcache (get, set,
 * delete, flush) API functions provided here.
 *
 * @param $bin The bin which is to be used.
 *
 * @param $flush Rebuild the bin/server/cache mapping.
 *
 * @return an Memcache object or FALSE.
 */
function dmemcache_object($bin = NULL, $flush = FALSE) {
  static $memcacheCache = array(), $memcache_servers, $memcache_bins;

  if ($flush) {
    foreach ($memcacheCache as $cluster) {
217
      memcache_close($cluster);
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
    }
    $memcacheCache = array();
  }

  if (empty($memcacheCache) || empty($memcacheCache[$bin])) {
    // $memcache_servers and $memcache_bins originate from settings.php.
    // $memcache_servers_custom and $memcache_bins_custom get set by
    // memcache.module. They are then merged into $memcache_servers and
    // $memcache_bins, which are statically cached for performance.
    if (empty($memcache_servers)) {
      // Values from settings.php
      $memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default'));
      $memcache_bins    = variable_get('memcache_bins', array('cache' => 'default'));
    }

    // If there is no cluster for this bin in $memcache_bins, cluster is 'default'.
    $cluster = empty($memcache_bins[$bin]) ? 'default' : $memcache_bins[$bin];

    // If this bin isn't in our $memcache_bins configuration array, and the
    // 'default' cluster is already initialized, map the bin to 'cache' because
    // we always map the 'cache' bin to the 'default' cluster.
    if (empty($memcache_bins[$bin]) && !empty($memcacheCache['cache'])) {
      $memcacheCache[$bin] = &$memcacheCache['cache'];
    }
    else {
      // Create a new Memcache object. Each cluster gets its own Memcache object.
244 245
      if (class_exists('Memcached')) {
        $memcache = new Memcached;
246 247 248 249 250 251 252 253 254
        $default_opts = array (
          Memcached::OPT_COMPRESSION => false,
          Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
          Memcached::OPT_BINARY_PROTOCOL => true,
        );
        $memconf = variable_get('memcache_options', $default_opts);
        foreach ($memconf as $key => $value) {
          $memcache->setOption($key, $value);
        }
255 256 257 258 259 260 261 262
      }
      else if (class_exists('Memcache')) {
        $memcache = new Memcache;
      }
      else {
        drupal_set_message(t('You must enable the PECL memcached or memcache extension to use memcache.inc.'), 'error');
        return;
      }
263 264 265 266 267 268 269 270 271
      // A variable to track whether we've connected to the first server.
      $init = FALSE;

      // Link all the servers to this cluster.
      foreach ($memcache_servers as $s => $c) {
        if ($c == $cluster) {
          list($host, $port) = explode(':', $s);

          // This is a server that belongs to this cluster.
272 273 274
          if (!class_exists('Memcached') && !$init) {
            // If using PECL memcache extension, use ->connect for first server
            if ($memcache->connect($host, $port)) {
275 276 277 278
              $init = TRUE;
            }
          }
          else {
279 280 281
            if ($memcache->addServer($host, $port) && !$init) {
              $init = TRUE;
            }
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
          }
        }
      }

      if ($init) {
        // Map the current bin with the new Memcache object.
        $memcacheCache[$bin] = $memcache;

        // Now that all the servers have been mapped to this cluster, look for
        // other bins that belong to the cluster and map them too.
        foreach ($memcache_bins as $b => $c) {
          if ($c == $cluster && $b != $bin) {
            // Map this bin and cluster by reference.
            $memcacheCache[$b] = &$memcacheCache[$bin];
          }
        }
      }
    }
  }

  return empty($memcacheCache[$bin]) ? FALSE : $memcacheCache[$bin];
}

function dmemcache_key($key, $bin = 'cache') {
  static $prefix;
  // memcache_key_prefix can be set in settings.php to support site namespaces
  // in a multisite environment.
  if (empty($prefix)) {
310 311 312 313 314 315 316 317 318 319 320
    if ($prefix = variable_get('memcache_key_prefix', '')) {
      $prefix .= '-';
    }
  }
  $full_key = urlencode($prefix . $bin . '-' . $key);

  // Memcache only supports key lengths up to 250 bytes.  If we have generated
  // a longer key, hash it with sha1 which will shrink the key down to 40 bytes
  // while still keeping it unique.
  if (strlen($full_key) > 250) {
    $full_key = $prefix . $bin . '-' . sha1($key);
321
  }
322

323
  return $full_key;
324
}