memcache.inc 9.43 KB
Newer Older
1
<?php
robertDouglass's avatar
robertDouglass committed
2
// $Id$
3

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

7 8 9 10 11 12
/*
 * A memcache API for Drupal.
 */

/**
 *  Place an item into memcache
13
 *
14 15 16 17 18 19 20 21 22 23 24
 * @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
 */
25
function dmemcache_set($key, $value, $exp = 0, $bin = 'cache') {
26 27 28
  global $_memcache_statistics;
  $_memcache_statistics['set'][] = $key;
  $_memcache_statistics['bins'][] = $bin;
29 30
  if ($mc = dmemcache_object($bin)) {
    $full_key = dmemcache_key($key, $bin);
31
    if (!$mc->set($full_key, $value, TRUE, $exp)) {
32
      watchdog('memcache', 'Failed to set key: ' . $full_key, WATCHDOG_ERROR);
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
    }
    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
 */
49
function dmemcache_get($key, $bin = 'cache') {
50 51 52
  global $_memcache_statistics;
  $_memcache_statistics['get'][] = $key;
  $_memcache_statistics['bins'][] = $bin;
53 54
  if ($mc = dmemcache_object($bin)) {
    $full_key = dmemcache_key($key, $bin);
55
    $result = $mc->get($full_key);
56
    if ($result) {
57
      $_memcache_statistics['hit'][] = $key;
58
    }
59
    return $result;
60 61 62 63 64 65 66 67 68 69 70
  }
}

/**
 * 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.
 */
71
function dmemcache_delete($key, $bin = 'cache') {
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
  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.
 */
89
function dmemcache_flush($bin = 'cache') {
90 91 92 93 94
  if ($mc = dmemcache_object($bin)) {
    return $mc->flush();
  }
}

95
function dmemcache_stats($bin = 'cache') {
96
  if ($mc = dmemcache_object($bin)) {
97
    return $mc->getExtendedStats();
98 99 100 101
  }
}

/**
102 103 104
 * 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
105
 * or if you need to use legacy code. Otherwise, use the dmemcache (get, set,
106
 * delete, flush) API functions provided here.
107
 *
108 109
 * @param $bin The bin which is to be used.
 *
110
 * @param $flush Rebuild the bin/server/cache mapping.
111 112 113
 *
 * @return an Memcache object or FALSE.
 */
114 115 116
function dmemcache_object($bin = NULL, $flush = FALSE) {
  static $memcacheCache = array(), $memcache_servers, $memcache_bins;

robertDouglass's avatar
robertDouglass committed
117
  if ($flush) {
118 119 120
    foreach ($memcacheCache as $cluster) {
      $cluster->close();
    }
121 122 123 124 125 126 127 128 129 130 131 132
    $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'));
133
    }
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148

    // 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.
      $memcache = new Memcache;
      // A variable to track whether we've connected to the first server.
      $init = FALSE;
robertDouglass's avatar
robertDouglass committed
149 150

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

robertDouglass's avatar
robertDouglass committed
155
          // This is a server that belongs to this cluster.
156
          if (!$init) {
robertDouglass's avatar
robertDouglass committed
157
            // First server gets the connect.
158
            if (@$memcache->addServer($host, $port)) {
159 160 161
              // Only set init to true if a connection was made.
              $init = TRUE;
            }
162
          }
163
          else {
robertDouglass's avatar
robertDouglass committed
164
            // Subsequent servers gett addServer.
165
            @$memcache->addServer($host, $port);
166 167
          }
        }
168 169
      }

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

174 175 176 177 178 179 180
        // 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];
          }
181 182 183
        }
      }
    }
184 185
  }

186
  return empty($memcacheCache[$bin]) ? FALSE : $memcacheCache[$bin];
187 188 189
}

function dmemcache_key($key, $bin = 'cache') {
190
  static $prefix;
191 192
  // memcache_key_prefix can be set in settings.php to support site namespaces
  // in a multisite environment.
193
  if (empty($prefix)) {
194 195
    $prefix = variable_get('memcache_key_prefix', '');
  }
196 197
  $full_key = ($prefix ? $prefix. '-' : '') . $bin . '-' . $key;
  return urlencode($full_key);
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
}


/** Implementation of cache.inc with memcache logic included **/

/**
 * Return data from the persistent cache.
 *
 * @param $key
 *   The cache ID of the data to retrieve.
 * @param $table
 *   The table $table to store the data in. Valid core values are 'cache_filter',
 *   'cache_menu', 'cache_page', or 'cache' for the default cache.
 */
function cache_get($key, $table = 'cache') {
213
  return dmemcache_get($key, $table);
214
}
215

216
/**
217
 * Store data in memcache.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
 *
 * @param $cid
 *   The cache ID of the data to store.
 * @param $table
 *   The table $table to store the data in. Valid core values are 'cache_filter',
 *   'cache_menu', 'cache_page', or 'cache'.
 * @param $data
 *   The data to store in the cache. Complex data types must be serialized first.
 * @param $expire
 *   One of the following values:
 *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
 *     explicitly told to using cache_clear_all() with a cache ID.
 *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
 *     general cache wipe.
 *   - A Unix timestamp: Indicates that the item should be kept at least until
 *     the given time, after which it behaves like CACHE_TEMPORARY.
 * @param $headers
 *   A string containing HTTP header information for cached pages.
236 237 238 239
 * @param $db_storage
 *   This boolean is unique to the memcache.inc implementation of cache set.
 *   It allows us to do a cache_set and not write to the database, but only
 *   to memcache.
240
 */
241
function cache_set($cid, $table = 'cache', $data, $expire = CACHE_PERMANENT, $headers = NULL) {
242 243 244 245 246 247 248 249 250 251 252
  // Create new cache object.
  $cache = new stdClass;
  $cache->cid = $cid;
  $cache->data = $data;
  $cache->created = time();
  $cache->expire = $expire;
  $cache->headers = $headers;

  // Save to memcache
  if ($expire == CACHE_PERMANENT || $expire > time()) {
    dmemcache_set($cid, $cache, $expire, $table);
253
  }
254 255 256 257
  else if ($expire == CACHE_TEMPORARY) {
    // A compromise for CACHE_TEMPORARY: Cache for three minutes.
    dmemcache_set($cid, $cache, 180, $table);
  }
258 259 260 261
}

/**
 *
262 263 264 265 266 267 268 269 270 271
 * Expire data from the cache. If called without arguments, expirable
 * entries will be cleared from the cache_page table.
 *
 * @param $cid
 *   If set, the cache ID to delete. Otherwise, all cache entries that can
 *   expire are deleted.
 *
 * @param $table
 *   If set, the table $table to delete from. Mandatory
 *   argument if $cid is set.
272
 *
273 274 275 276
 * @param $wildcard
 *   If set to TRUE, the $cid is treated as a substring
 *   to match rather than a complete ID. The match is a right hand
 *   match. If '*' is given as $cid, the table $table will be emptied.
277
 */
278 279 280 281 282
function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
  // Memcache logic is simpler because memcache doesn't have a minimum cache
  // lifetime consideration (it handles it internally), and doesn't support
  // wildcards.
  $bin = empty($table) ? 'cache' : $table;
283
  if (empty($cid) || $cid == '*') {
284 285 286 287 288 289
    dmemcache_flush($table);
  }
  else {
    dmemcache_delete($cid, $table);
  }
}