dmemcache.inc 11.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
<?php

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

global $_memcache_statistics;
12
$_memcache_statistics = array();
13 14 15 16 17 18 19 20

/*
 * A memcache API for Drupal.
 */

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

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
/**
 *  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
 */
71 72
function dmemcache_add($key, $value, $exp = 0, $bin = 'cache', $mc = NULL, $flag = FALSE) {
  global $_memcache_statistics;
73 74
  $full_key = dmemcache_key($key, $bin);
  $_memcache_statistics[] = array('add', $bin, $full_key, '');
75 76
  if ($mc || ($mc = dmemcache_object($bin))) {
    if (class_exists('Memcached')) {
Jeremy's avatar
Jeremy committed
77
      return $mc->add($full_key, $value, $exp);
78 79
    }
    else {
Jeremy's avatar
Jeremy committed
80
      return $mc->add($full_key, $value, $flag, $exp);
81 82
    }
  }
83
  return FALSE;
84 85
}

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

/**
 * 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
 */
function dmemcache_get_multi($keys, $bin = 'cache', $mc = NULL) {
  global $_memcache_statistics;
121 122 123 124 125 126 127
  $full_keys = array();
  $statistics = array();
  foreach ($keys as $key => $cid) {
    $full_key = dmemcache_key($cid, $bin);
    $statistics[$full_key] = array('getMulti', $bin, $full_key);
    $full_keys[] = $full_key;
  }
128 129 130 131 132 133
  $results = array();
  if ($mc || ($mc = dmemcache_object($bin))) {
    if (class_exists('Memcached')) {
      $results = $mc->getMulti($full_keys);
    }
    else {
134
      $results = $mc->get($full_keys);
135 136
    }
  }
137 138 139 140
  foreach ($statistics as $key => $values) {
    $values[] = isset($results[$key]) ? '1': '0';
    $_memcache_statistics[] = $values;
  }
141 142 143 144 145

  // If $results is FALSE, convert it to an empty array.
  if (!$results) {
    $results = array();
  }
146 147 148 149 150 151 152 153 154

  // Convert the full keys back to the cid.
  $cid_results = array();
  foreach ($results as $value) {
    if (is_object($value)) {
      $cid_results[$value->cid] = $value;
    }
  }
  return $cid_results;
155 156 157 158 159 160 161 162 163 164
}

/**
 * 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.
 */
165
function dmemcache_delete($key, $bin = 'cache', $mc = NULL) {
166 167 168
  global $_memcache_statistics;
  $full_key = dmemcache_key($key, $bin);
  $_memcache_statistics[] = array('delete', $bin, $full_key, '');
169
  if ($mc || ($mc = dmemcache_object($bin))) {
170
    return $mc->delete($full_key, 0);
171 172 173 174 175 176 177 178 179 180 181 182 183 184
  }
  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.
 */
185
function dmemcache_flush($bin = 'cache', $mc = NULL) {
186 187
  global $_memcache_statistics;
  $_memcache_statistics[] = array('flush', $bin, '', '');
188
  if ($mc || ($mc = dmemcache_object($bin))) {
189
    return memcache_flush($mc);
190 191 192 193
  }
}

function dmemcache_stats($bin = 'cache', $type = '') {
194 195 196 197 198 199 200 201
  // resolve requests for 'default' type to ''
  if ($type == 'default') {
    $type = '';
  }
  // resolve requests for 'default' bin to 'cache'.
  if ($bin == 'default') {
    $bin = 'cache';
  }
202
  if ($mc = dmemcache_object($bin)) {
203 204 205
    if (class_exists('Memcached')) {
      return $mc->getStats();
    }
206 207
    // 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}.
208
    // If $type is 'default', then no parameter should be passed to the
209 210
    // Memcache memcache_get_extended_stats() function.
    if ($type == 'default' || $type == '') {
211 212 213 214 215 216
      if (class_exists('Memcached')) {
        return $mc->getStats();
      }
      else if (class_exists('Memcache')) {
        return $mc->getExtendedStats();
      }
217 218
    }
    else {
219
      if (class_exists('Memcached')) {
robertDouglass's avatar
robertDouglass committed
220
        return $mc->getStats();
221 222 223 224
      }
      else if (class_exists('Memcache')) {
        return $mc->getExtendedStats($type);
      }
225
    }
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
  }
}

/**
 * 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) {
247
      memcache_close($cluster);
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
    }
    $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.
274 275
      if (class_exists('Memcached')) {
        $memcache = new Memcached;
276 277
        $default_opts = array(
          Memcached::OPT_COMPRESSION => FALSE,
278 279
          Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
        );
280 281 282 283
        foreach ($default_opts as $key => $value) {
          $memcache->setOption($key, $value);
        }
        $memconf = variable_get('memcache_options', array());
284 285 286
        foreach ($memconf as $key => $value) {
          $memcache->setOption($key, $value);
        }
287 288 289 290 291 292 293 294
      }
      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;
      }
295 296 297 298 299 300 301 302 303
      // 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.
304 305 306
          if (!class_exists('Memcached') && !$init) {
            // If using PECL memcache extension, use ->connect for first server
            if ($memcache->connect($host, $port)) {
307 308 309 310
              $init = TRUE;
            }
          }
          else {
311 312 313
            if ($memcache->addServer($host, $port) && !$init) {
              $init = TRUE;
            }
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
          }
        }
      }

      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') {
338 339 340 341 342 343 344 345
  $prefix = '';
  if ($prefix = variable_get('memcache_key_prefix', '')) {
    $prefix .= '-';
  }
  // When simpletest is running, emulate the simpletest database prefix here
  // to avoid the child site setting cache entries in the parent site.
  if (isset($GLOBALS['drupal_test_info']['test_run_id'])) {
    $prefix .= $GLOBALS['drupal_test_info']['test_run_id'];
346 347 348 349 350 351 352 353
  }
  $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);
354
  }
355

356
  return $full_key;
357
}