2008-05-18 06:22:34 +02:00
|
|
|
require 'memcache'
|
|
|
|
|
|
|
|
module ActiveSupport
|
|
|
|
module Cache
|
2008-11-24 22:53:39 +01:00
|
|
|
# A cache store implementation which stores data in Memcached:
|
|
|
|
# http://www.danga.com/memcached/
|
|
|
|
#
|
|
|
|
# This is currently the most popular cache store for production websites.
|
|
|
|
#
|
|
|
|
# Special features:
|
|
|
|
# - Clustering and load balancing. One can specify multiple memcached servers,
|
|
|
|
# and MemCacheStore will load balance between all available servers. If a
|
|
|
|
# server goes down, then MemCacheStore will ignore it until it goes back
|
|
|
|
# online.
|
|
|
|
# - Time-based expiry support. See #write and the +:expires_in+ option.
|
2009-02-04 21:26:08 +01:00
|
|
|
# - Per-request in memory cache for all communication with the MemCache server(s).
|
2008-05-18 06:22:34 +02:00
|
|
|
class MemCacheStore < Store
|
2008-11-24 22:53:39 +01:00
|
|
|
module Response # :nodoc:
|
2008-05-18 06:22:34 +02:00
|
|
|
STORED = "STORED\r\n"
|
|
|
|
NOT_STORED = "NOT_STORED\r\n"
|
|
|
|
EXISTS = "EXISTS\r\n"
|
|
|
|
NOT_FOUND = "NOT_FOUND\r\n"
|
|
|
|
DELETED = "DELETED\r\n"
|
|
|
|
end
|
|
|
|
|
2009-08-04 17:16:03 +02:00
|
|
|
def self.build_mem_cache(*addresses)
|
|
|
|
addresses = addresses.flatten
|
|
|
|
options = addresses.extract_options!
|
|
|
|
addresses = ["localhost"] if addresses.empty?
|
|
|
|
MemCache.new(addresses, options)
|
|
|
|
end
|
2008-05-18 06:22:34 +02:00
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
# Creates a new MemCacheStore object, with the given memcached server
|
|
|
|
# addresses. Each address is either a host name, or a host-with-port string
|
|
|
|
# in the form of "host_name:port". For example:
|
|
|
|
#
|
|
|
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
|
|
|
#
|
|
|
|
# If no addresses are specified, then MemCacheStore will connect to
|
|
|
|
# localhost port 11211 (the default memcached port).
|
2009-12-01 02:38:34 +01:00
|
|
|
#
|
|
|
|
# Instead of addresses one can pass in a MemCache-like object. For example:
|
|
|
|
#
|
|
|
|
# require 'memcached' # gem install memcached; uses C bindings to libmemcached
|
|
|
|
# ActiveSupport::Cache::MemCacheStore.new(Memcached::Rails.new("localhost:11211"))
|
2008-05-18 06:22:34 +02:00
|
|
|
def initialize(*addresses)
|
2009-08-04 17:16:03 +02:00
|
|
|
if addresses.first.respond_to?(:get)
|
|
|
|
@data = addresses.first
|
|
|
|
else
|
|
|
|
@data = self.class.build_mem_cache(*addresses)
|
|
|
|
end
|
2009-02-04 21:26:08 +01:00
|
|
|
|
|
|
|
extend Strategy::LocalCache
|
2008-05-18 06:22:34 +02:00
|
|
|
end
|
|
|
|
|
2009-08-04 17:16:03 +02:00
|
|
|
# Reads multiple keys from the cache.
|
|
|
|
def read_multi(*keys)
|
|
|
|
@data.get_multi keys
|
|
|
|
end
|
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
def read(key, options = nil) # :nodoc:
|
2008-05-18 06:22:34 +02:00
|
|
|
super
|
|
|
|
@data.get(key, raw?(options))
|
|
|
|
rescue MemCache::MemCacheError => e
|
|
|
|
logger.error("MemCacheError (#{e}): #{e.message}")
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
# Writes a value to the cache.
|
|
|
|
#
|
|
|
|
# Possible options:
|
|
|
|
# - +:unless_exist+ - set to true if you don't want to update the cache
|
|
|
|
# if the key is already set.
|
|
|
|
# - +:expires_in+ - the number of seconds that this value may stay in
|
|
|
|
# the cache. See ActiveSupport::Cache::Store#write for an example.
|
2008-05-18 06:22:34 +02:00
|
|
|
def write(key, value, options = nil)
|
|
|
|
super
|
|
|
|
method = options && options[:unless_exist] ? :add : :set
|
2008-10-27 07:47:01 +01:00
|
|
|
# memcache-client will break the connection if you send it an integer
|
|
|
|
# in raw mode, so we convert it to a string to be sure it continues working.
|
|
|
|
value = value.to_s if raw?(options)
|
2008-05-18 06:22:34 +02:00
|
|
|
response = @data.send(method, key, value, expires_in(options), raw?(options))
|
|
|
|
response == Response::STORED
|
|
|
|
rescue MemCache::MemCacheError => e
|
|
|
|
logger.error("MemCacheError (#{e}): #{e.message}")
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
def delete(key, options = nil) # :nodoc:
|
2008-05-18 06:22:34 +02:00
|
|
|
super
|
|
|
|
response = @data.delete(key, expires_in(options))
|
|
|
|
response == Response::DELETED
|
|
|
|
rescue MemCache::MemCacheError => e
|
|
|
|
logger.error("MemCacheError (#{e}): #{e.message}")
|
|
|
|
false
|
2008-06-02 08:35:38 +02:00
|
|
|
end
|
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
def exist?(key, options = nil) # :nodoc:
|
2008-06-02 08:35:38 +02:00
|
|
|
# Doesn't call super, cause exist? in memcache is in fact a read
|
|
|
|
# But who cares? Reading is very fast anyway
|
2009-02-04 21:26:08 +01:00
|
|
|
# Local cache is checked first, if it doesn't know then memcache itself is read from
|
2008-06-02 08:35:38 +02:00
|
|
|
!read(key, options).nil?
|
|
|
|
end
|
2008-05-18 06:22:34 +02:00
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
def increment(key, amount = 1) # :nodoc:
|
2008-05-18 06:22:34 +02:00
|
|
|
log("incrementing", key, amount)
|
2008-10-27 07:47:01 +01:00
|
|
|
|
|
|
|
response = @data.incr(key, amount)
|
2008-05-18 06:22:34 +02:00
|
|
|
response == Response::NOT_FOUND ? nil : response
|
2008-10-27 07:47:01 +01:00
|
|
|
rescue MemCache::MemCacheError
|
2008-05-18 06:22:34 +02:00
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
def decrement(key, amount = 1) # :nodoc:
|
2008-05-18 06:22:34 +02:00
|
|
|
log("decrement", key, amount)
|
2008-10-27 07:47:01 +01:00
|
|
|
response = @data.decr(key, amount)
|
2008-05-18 06:22:34 +02:00
|
|
|
response == Response::NOT_FOUND ? nil : response
|
2008-10-27 07:47:01 +01:00
|
|
|
rescue MemCache::MemCacheError
|
2008-05-18 06:22:34 +02:00
|
|
|
nil
|
2008-10-27 07:47:01 +01:00
|
|
|
end
|
|
|
|
|
2008-11-24 22:53:39 +01:00
|
|
|
def delete_matched(matcher, options = nil) # :nodoc:
|
2009-02-04 21:26:08 +01:00
|
|
|
# don't do any local caching at present, just pass
|
|
|
|
# through and let the error happen
|
2008-05-18 06:22:34 +02:00
|
|
|
super
|
|
|
|
raise "Not supported by Memcache"
|
2008-10-27 07:47:01 +01:00
|
|
|
end
|
|
|
|
|
2008-05-18 06:22:34 +02:00
|
|
|
def clear
|
|
|
|
@data.flush_all
|
2008-10-27 07:47:01 +01:00
|
|
|
end
|
|
|
|
|
2008-05-18 06:22:34 +02:00
|
|
|
def stats
|
|
|
|
@data.stats
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def raw?(options)
|
|
|
|
options && options[:raw]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|