lib/redis_failover/client.rb in redis_failover-0.5.4 vs lib/redis_failover/client.rb in redis_failover-0.6.0

- old
+ new

@@ -1,14 +1,34 @@ -require 'set' - module RedisFailover - # Redis failover-aware client. + # Redis failover-aware client. RedisFailover::Client is a wrapper over a set of underlying redis + # clients, which means all normal redis operations can be performed on an instance of this class. + # The class only requires a set of ZooKeeper server addresses to function properly. The client + # will automatically retry failed operations, and handle failover to a new master. The client + # registers and listens for watcher events from the Node Manager. When these events are received, + # the client fetches the latest set of redis nodes from ZooKeeper and rebuilds its internal + # Redis clients appropriately. RedisFailover::Client also directs write operations to the master, + # and all read operations to the slaves. + # + # Examples + # + # client = RedisFailover::Client.new(:zkservers => 'localhost:2181,localhost:2182,localhost:2183') + # client.set('foo', 1) # will be directed to master + # client.get('foo') # will be directed to a slave + # class Client include Util + # Maximum allowed elapsed time between notifications from the Node Manager. + # When this timeout is reached, the client will raise a NoNodeManagerError + # and purge its internal redis clients. ZNODE_UPDATE_TIMEOUT = 9 + + # Amount of time to sleep before retrying a failed operation. RETRY_WAIT_TIME = 3 + + # Redis read operations that are automatically dispatched to slaves. Any + # operation not listed here will be dispatched to the master. REDIS_READ_OPS = Set[ :echo, :exists, :get, :getbit, @@ -45,10 +65,12 @@ :zrevrangebyscore, :zrevrank, :zscore ].freeze + # Unsupported Redis operations. These don't make sense in a client + # that abstracts the master/slave servers. UNSUPPORTED_OPS = Set[ :select, :ttl, :dbsize, ].freeze @@ -64,33 +86,36 @@ # Creates a new failover redis client. # # Options: # - # :zkservers - comma-separated ZooKeeper host:port pairs (required) - # :znode_path - the Znode path override for redis server list (optional) - # :password - password for redis nodes (optional) - # :namespace - namespace for redis nodes (optional) - # :logger - logger override (optional) + # :zkservers - comma-separated ZooKeeper host:port pairs (required) + # :znode_path - the Znode path override for redis server list (optional) + # :password - password for redis nodes (optional) + # :db - db to use for redis nodes (optional) + # :namespace - namespace for redis nodes (optional) + # :logger - logger override (optional) # :retry_failure - indicate if failures should be retried (default true) - # :max_retries - max retries for a failure (default 3) + # :max_retries - max retries for a failure (default 3) # def initialize(options = {}) Util.logger = options[:logger] if options[:logger] @zkservers = options.fetch(:zkservers) { raise ArgumentError, ':zkservers required'} @znode = options[:znode_path] || Util::DEFAULT_ZNODE_PATH @namespace = options[:namespace] @password = options[:password] + @db = options[:db] @retry = options[:retry_failure] || true @max_retries = @retry ? options.fetch(:max_retries, 3) : 1 @master = nil @slaves = [] @lock = Monitor.new setup_zookeeper_client build_clients end + # Dispatches redis operations to master/slaves. def method_missing(method, *args, &block) if redis_operation?(method) dispatch(method, *args, &block) else super @@ -100,11 +125,11 @@ def respond_to?(method) redis_operation?(method) || super end def inspect - "#<RedisFailover::Client - master: #{master_name}, slaves: #{slave_names})>" + "#<RedisFailover::Client (master: #{master_name}, slaves: #{slave_names})>" end alias_method :to_s, :inspect private @@ -161,17 +186,18 @@ else # direct everything else to master master.send(method, *args, &block) end rescue *CONNECTIVITY_ERRORS => ex - logger.error("Error while handling operation `#{method}` - #{ex.inspect}") + logger.error("Error while handling `#{method}` - #{ex.inspect}") logger.error(ex.backtrace.join("\n")) if tries < @max_retries tries += 1 build_clients - sleep(RETRY_WAIT_TIME) && retry + sleep(RETRY_WAIT_TIME) + retry end raise end end @@ -212,11 +238,12 @@ logger.error("Failed to fetch nodes from #{@zkservers} - #{ex.inspect}") logger.error(ex.backtrace.join("\n")) if tries < @max_retries tries += 1 - sleep(RETRY_WAIT_TIME) && retry + sleep(RETRY_WAIT_TIME) + retry end raise end end @@ -231,10 +258,13 @@ end def new_clients_for(*nodes) nodes.map do |node| host, port = node.split(':') - client = Redis.new(:host => host, :port => port, :password => @password) + opts = {:host => host, :port => port} + opts.update(:db => @db) if @db + opts.update(:password => @password) if @password + client = Redis.new(opts) if @namespace client = Redis::Namespace.new(@namespace, :redis => client) end client end