Skip to content
This repository has been archived by the owner on May 29, 2024. It is now read-only.

Commit

Permalink
Merge pull request #15 from Sage/feature_optional_redis_cache
Browse files Browse the repository at this point in the history
OptionalRedisCacheStore
  • Loading branch information
vaughanbrittonsage authored Feb 28, 2018
2 parents 103a0fd + 634f67e commit 84fcdb6
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 220 deletions.
1 change: 1 addition & 0 deletions cache_store_redis/.rspec
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require spec_helper
--color
--require spec_helper
--format doc
211 changes: 2 additions & 209 deletions cache_store_redis/lib/cache_store_redis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,212 +5,5 @@
require 'redis'
require 'securerandom'

# This class is used to implement a redis cache store.
class RedisCacheStore
def initialize(namespace = nil, config = nil)
unless RUBY_PLATFORM == 'java'
require 'oj'
end

@namespace = namespace
@config = config
@queue = Queue.new

@connections_created = 0
@connections_in_use = 0
@mutex = Mutex.new
@enable_stats = false
end

def enable_stats=(value)
@enable_stats = value
end

def increment_created_stat
@mutex.synchronize do
@connections_created += 1
end
end

def increment_using_stat
@mutex.synchronize do
@connections_in_use += 1
end
end

def decrement_using_stat
@mutex.synchronize do
@connections_in_use -= 1
end
end

# This method is called to configure the connection to the cache store.
def configure(host = 'localhost',
port = 6379,
db = 'default',
password = nil,
driver: nil,
url: nil,
connect_timeout: 0.5,
read_timeout: 1,
write_timeout: 0.5)
if !url.nil?
@config = {}
@config[:url] = url
@config[:db] = db
else
@config = { host: host, port: port, db: db }
end

@config[:password] = password unless password.nil?
@config[:driver] = driver unless driver.nil?

@config[:connect_timeout] = connect_timeout
@config[:read_timeout] = read_timeout
@config[:write_timeout] = write_timeout
end

def fetch_client
begin
@queue.pop(true)
rescue
increment_created_stat
Redis.new(@config)
end
end

def clean
while @queue.length.positive?
client = @queue.pop(true)
client.close
end
end

def log_stats
return unless @enable_stats == true
S1Logging.logger.debug do
"[#{self.class}] - REDIS Connection Stats. Process: #{Process.pid} | " \
"Created: #{@connections_created} | Pending: #{@queue.length} | In use: #{@connections_in_use}"
end
end

def with_client
log_stats
begin
client = fetch_client
increment_using_stat
log_stats
yield client
ensure
@queue.push(client)
decrement_using_stat
log_stats
end
end

# This method is called to set a value within this cache store by it's key.
#
# @param key [String] This is the unique key to reference the value being set within this cache store.
# @param value [Object] This is the value to set within this cache store.
# @param expires_in [Integer] This is the number of seconds from the current time that this value should expire.
def set(key, value, expires_in = 0)
k = build_key(key)

v = if value.nil? || (value.is_a?(String) && value.strip.empty?)
nil
else
serialize(value)
end

with_client do |client|
client.multi do
client.set(k, v)

client.expire(k, expires_in) if expires_in.positive?
end
end
end

# This method is called to get a value from this cache store by it's unique key.
#
# @param key [String] This is the unique key to reference the value to fetch from within this cache store.
# @param expires_in [Integer] This is the number of seconds from the current time that this value should expire.
# (This is used in conjunction with the block to hydrate the cache key if it is empty.)
# @param &block [Block] This block is provided to hydrate this cache store with the value for the request key
# when it is not found.
# @return [Object] The value for the specified unique key within the cache store.
def get(key, expires_in = 0, &block)
k = build_key(key)

value = with_client do |client|
client.get(k)
end

if !value.nil? && value.strip.empty?
value = nil
else
value = deserialize(value) unless value.nil?
end

if value.nil? && block_given?
value = yield
set(key, value, expires_in)
end

value
end

# This method is called to remove a value from this cache store by it's unique key.
#
# @param key [String] This is the unique key to reference the value to remove from this cache store.
def remove(key)
with_client do |client|
client.del(build_key(key))
end
end

# This method is called to check if a value exists within this cache store for a specific key.
#
# @param key [String] This is the unique key to reference the value to check for within this cache store.
# @return [Boolean] True or False to specify if the key exists in the cache store.
def exist?(key)
with_client do |client|
client.exists(build_key(key))
end
end

# Ping the cache store.
#
# @return [String] `PONG`
def ping
with_client do |client|
client.ping
end
end

private

def serialize(object)
if RUBY_PLATFORM == 'java'
Marshal::dump(object)
else
Oj.dump(object)
end
end

def deserialize(object)
if RUBY_PLATFORM == 'java'
Marshal::load(object)
else
Oj.load(object)
end
end

def build_key(key)
if !@namespace.nil?
@namespace + ':' + key.to_s
else
key.to_s
end
end
end
require_relative 'cache_store_redis/redis_cache_store'
require_relative 'cache_store_redis/optional_redis_cache_store'
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require 'logger'

# This class is used to define a redis cache store that logs failures as warnings but does not raise errors for
# cache connections
class OptionalRedisCacheStore
def initialize(namespace: nil, config: nil, logger: nil)
@cache_store = RedisCacheStore.new(namespace, config)
@logger = logger || Logger.new(STDOUT)
end

def redis_store
@cache_store
end

# This method is called to configure the connection to the cache store.
def configure(
host = 'localhost',
port = 6379,
db = 'default',
password = nil,
driver: nil,
url: nil,
connect_timeout: 0.5,
read_timeout: 1,
write_timeout: 0.5)
redis_store.configure(
host,
port,
db,
password,
driver: driver,
url: url,
connect_timeout: connect_timeout,
read_timeout: read_timeout,
write_timeout: write_timeout
)
end

def optional_get(key, expires_in = 0)
redis_store.get(key, expires_in)
rescue => e
@logger.error(
"[#{self.class}] - An error occurred requesting data from the cache. " \
"Key: #{key} | Error: #{e.message} | Backtrace: #{e.backtrace}"
)
nil
end

def get(key, expires_in = 0, &block)
value = optional_get(key, expires_in)

if value.nil? && block_given?
value = yield
set(key, value, expires_in)
end

value
end

def set(key, value, expires_in = 0)
redis_store.set(key, value, expires_in)
rescue => e
@logger.error(
"[#{self.class}] - An error occurred storing data in the cache. " \
"Key: #{key} | Error: #{e.message} | Backtrace: #{e.backtrace}"
)
end

def remove(key)
redis_store.remove(key)
rescue => e
@logger.error(
"[#{self.class}] - An error occurred removing data from the cache. " \
"Key: #{key} | Error: #{e.message} | Backtrace: #{e.backtrace}"
)
end

def exist?(key)
redis_store.exist?(key)
rescue => e
@logger.error(
"[#{self.class}] - An error occurred checking if a key exists in the cache. " \
"Key: #{key} | Error: #{e.message} | Backtrace: #{e.backtrace}"
)
false
end

def ping
redis_store.ping
rescue => e
@logger.error(
"[#{self.class}] - An error occurred checking pinging the cache. " \
"Error: #{e.message} | Backtrace: #{e.backtrace}"
)
false
end
end
Loading

0 comments on commit 84fcdb6

Please sign in to comment.