lib/redis_failover/client.rb in redis_failover-0.1.1 vs lib/redis_failover/client.rb in redis_failover-0.2.0
- old
+ new
@@ -4,10 +4,11 @@
module RedisFailover
# Redis failover-aware client.
class Client
include Util
+ RETRY_WAIT_TIME = 3
REDIS_ERRORS = Errno.constants.map { |c| Errno.const_get(c) }.freeze
REDIS_READ_OPS = Set[
:dbsize,
:echo,
:exists,
@@ -81,12 +82,12 @@
Util.logger = options[:logger] if options[:logger]
@namespace = options[:namespace]
@password = options[:password]
@retry = options[:retry_failure] || true
- @max_retries = options[:max_retries] || 3
- @registry_url = "http://#{options[:host]}:#{options[:port]}/redis_servers"
+ @max_retries = @retry ? options.fetch(:max_retries, 3) : 0
+ @server_url = "http://#{options[:host]}:#{options[:port]}/redis_servers"
@redis_servers = nil
@master = nil
@slaves = []
@lock = Mutex.new
build_clients
@@ -115,61 +116,78 @@
Redis.public_instance_methods(false).include?(method)
end
def dispatch(method, *args, &block)
tries = 0
+
begin
if REDIS_READ_OPS.include?(method)
# send read operations to a slave
slave.send(method, *args, &block)
else
# direct everything else to master
master.send(method, *args, &block)
end
- rescue NoMasterError, *REDIS_ERRORS
+ rescue Error, *REDIS_ERRORS
logger.error("No suitable node available for operation `#{method}.`")
- sleep(3)
build_clients
- if @retry && tries < @max_retries
+ if tries < @max_retries
tries += 1
- retry
+ sleep(RETRY_WAIT_TIME) && retry
end
raise
end
end
def master
- @master or raise NoMasterError
+ if @master
+ verify_role!(@master, :master)
+ return @master
+ end
+ raise NoMasterError
end
def slave
# pick a slave, if none available fallback to master
- @slaves.sample || master
+ if slave = @slaves.sample
+ verify_role!(slave, :slave)
+ return slave
+ end
+ master
end
def build_clients
@lock.synchronize do
+ tries = 0
+
begin
logger.info('Attempting to fetch nodes and build redis clients.')
servers = fetch_redis_servers
master = new_clients_for(servers[:master]).first if servers[:master]
slaves = new_clients_for(*servers[:slaves])
# once clients are successfully created, swap the references
@master = master
@slaves = slaves
rescue => ex
- logger.error("Failed to fetch servers from #{@registry_url} - #{ex.message}")
+ logger.error("Failed to fetch servers from #{@server_url} - #{ex.message}")
logger.error(ex.backtrace.join("\n"))
+
+ if tries < @max_retries
+ tries += 1
+ sleep(RETRY_WAIT_TIME) && retry
+ end
+
+ raise FailoverServerUnreachableError.new(@server_url)
end
end
end
def fetch_redis_servers
- open(@registry_url) do |io|
+ open(@server_url) do |io|
servers = symbolize_keys(MultiJson.decode(io))
logger.info("Fetched servers: #{servers}")
servers
end
end
@@ -185,14 +203,26 @@
end
end
def master_info
return "none" unless @master
- "#{@master.client.host}:#{@master.client.port}"
+ name_for(@master)
end
def slaves_info
return "none" if @slaves.empty?
- @slaves.map { |s| "#{s.client.host}:#{s.client.port}" }.join(', ')
+ @slaves.map { |slave| name_for(slave) }.join(', ')
+ end
+
+ def verify_role!(node, role)
+ current_role = node.info['role']
+ if current_role.to_sym != role
+ raise InvalidNodeRoleError.new(name_for(node), role, current_role)
+ end
+ role
+ end
+
+ def name_for(node)
+ "#{node.client.host}:#{node.client.port}"
end
end
end