redis-slave-read 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +43 -0
- data/Rakefile +9 -0
- data/lib/activesupport/lib/active_support/cache/redis_slave_read_cache.rb +185 -0
- data/lib/redis-slave-read.rb +3 -0
- data/lib/redis-slave-read/interface/base.rb +106 -0
- data/lib/redis-slave-read/interface/hiredis.rb +30 -0
- data/lib/redis-slave-read/version.rb +5 -0
- data/redis-slave-read.gemspec +22 -0
- data/spec/interface/hiredis_spec.rb +76 -0
- data/spec/spec_helper.rb +8 -0
- metadata +93 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Chris Heald
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Redis::SlaveRead
|
2
|
+
|
3
|
+
Provides for distribution of slave reads in a Redis cluster.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'redis-slave-read'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install redis-slave-read
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Rather than using a Redis instance, create a wrapper that wraps multiple Redis connections.
|
22
|
+
|
23
|
+
master = Redis.new "localhost:6379"
|
24
|
+
slave1 = Redis.new "localhost:6389"
|
25
|
+
slave2 = Redis.new "localhost:6399"
|
26
|
+
$redis = Redis::SlaveRead::Interface::HiRedis.new(master: master, slaves: [slave1, slave2])
|
27
|
+
|
28
|
+
Make sure that your slaves are set to be slaved to the master, like `slaveof localhost 6379`
|
29
|
+
|
30
|
+
Now, you can treat your SlaveRead interface as a normal Redis interfaces. Reads are distributed
|
31
|
+
among the slaves, and writes are always sent to the master. Writes will be propagated to slaves
|
32
|
+
by the master.
|
33
|
+
|
34
|
+
$redis.set "foo", "bar"
|
35
|
+
$redis.get "foo"
|
36
|
+
|
37
|
+
## Contributing
|
38
|
+
|
39
|
+
1. Fork it
|
40
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
41
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
42
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
43
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module Cache
|
3
|
+
class RedisStore < Store
|
4
|
+
# Instantiate the store.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
# RedisStore.new
|
8
|
+
# # => host: localhost, port: 6379, db: 0
|
9
|
+
#
|
10
|
+
# RedisStore.new "example.com"
|
11
|
+
# # => host: example.com, port: 6379, db: 0
|
12
|
+
#
|
13
|
+
# RedisStore.new "example.com:23682"
|
14
|
+
# # => host: example.com, port: 23682, db: 0
|
15
|
+
#
|
16
|
+
# RedisStore.new "example.com:23682/1"
|
17
|
+
# # => host: example.com, port: 23682, db: 1
|
18
|
+
#
|
19
|
+
# RedisStore.new "example.com:23682/1/theplaylist"
|
20
|
+
# # => host: example.com, port: 23682, db: 1, namespace: theplaylist
|
21
|
+
#
|
22
|
+
# RedisStore.new "localhost:6379/0", "localhost:6380/0"
|
23
|
+
# # => instantiate a cluster
|
24
|
+
def initialize(*addresses)
|
25
|
+
@data = ::Redis::Factory.create(addresses)
|
26
|
+
super(addresses.extract_options!)
|
27
|
+
end
|
28
|
+
|
29
|
+
def write(name, value, options = nil)
|
30
|
+
options = merged_options(options)
|
31
|
+
instrument(:write, name, options) do |payload|
|
32
|
+
entry = options[:raw].present? ? value : Entry.new(value, options)
|
33
|
+
write_entry(namespaced_key(name, options), entry, options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Delete objects for matched keys.
|
38
|
+
#
|
39
|
+
# Example:
|
40
|
+
# cache.del_matched "rab*"
|
41
|
+
def delete_matched(matcher, options = nil)
|
42
|
+
options = merged_options(options)
|
43
|
+
instrument(:delete_matched, matcher.inspect) do
|
44
|
+
matcher = key_matcher(matcher, options)
|
45
|
+
begin
|
46
|
+
!(keys = @data.keys(matcher)).empty? && @data.del(*keys)
|
47
|
+
rescue Errno::ECONNREFUSED => e
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Reads multiple keys from the cache using a single call to the
|
54
|
+
# servers for all keys. Options can be passed in the last argument.
|
55
|
+
#
|
56
|
+
# Example:
|
57
|
+
# cache.read_multi "rabbit", "white-rabbit"
|
58
|
+
# cache.read_multi "rabbit", "white-rabbit", :raw => true
|
59
|
+
def read_multi(*names)
|
60
|
+
values = @data.mget(*names)
|
61
|
+
|
62
|
+
# Remove the options hash before mapping keys to values
|
63
|
+
names.extract_options!
|
64
|
+
|
65
|
+
result = Hash[names.zip(values)]
|
66
|
+
result.reject!{ |k,v| v.nil? }
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
# Increment a key in the store.
|
71
|
+
#
|
72
|
+
# If the key doesn't exist it will be initialized on 0.
|
73
|
+
# If the key exist but it isn't a Fixnum it will be initialized on 0.
|
74
|
+
#
|
75
|
+
# Example:
|
76
|
+
# We have two objects in cache:
|
77
|
+
# counter # => 23
|
78
|
+
# rabbit # => #<Rabbit:0x5eee6c>
|
79
|
+
#
|
80
|
+
# cache.increment "counter"
|
81
|
+
# cache.read "counter", :raw => true # => "24"
|
82
|
+
#
|
83
|
+
# cache.increment "counter", 6
|
84
|
+
# cache.read "counter", :raw => true # => "30"
|
85
|
+
#
|
86
|
+
# cache.increment "a counter"
|
87
|
+
# cache.read "a counter", :raw => true # => "1"
|
88
|
+
#
|
89
|
+
# cache.increment "rabbit"
|
90
|
+
# cache.read "rabbit", :raw => true # => "1"
|
91
|
+
def increment(key, amount = 1)
|
92
|
+
instrument(:increment, key, :amount => amount) do
|
93
|
+
@data.incrby key, amount
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Decrement a key in the store
|
98
|
+
#
|
99
|
+
# If the key doesn't exist it will be initialized on 0.
|
100
|
+
# If the key exist but it isn't a Fixnum it will be initialized on 0.
|
101
|
+
#
|
102
|
+
# Example:
|
103
|
+
# We have two objects in cache:
|
104
|
+
# counter # => 23
|
105
|
+
# rabbit # => #<Rabbit:0x5eee6c>
|
106
|
+
#
|
107
|
+
# cache.decrement "counter"
|
108
|
+
# cache.read "counter", :raw => true # => "22"
|
109
|
+
#
|
110
|
+
# cache.decrement "counter", 2
|
111
|
+
# cache.read "counter", :raw => true # => "20"
|
112
|
+
#
|
113
|
+
# cache.decrement "a counter"
|
114
|
+
# cache.read "a counter", :raw => true # => "-1"
|
115
|
+
#
|
116
|
+
# cache.decrement "rabbit"
|
117
|
+
# cache.read "rabbit", :raw => true # => "-1"
|
118
|
+
def decrement(key, amount = 1)
|
119
|
+
instrument(:decrement, key, :amount => amount) do
|
120
|
+
@data.decrby key, amount
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Clear all the data from the store.
|
125
|
+
def clear
|
126
|
+
instrument(:clear, nil, nil) do
|
127
|
+
@data.flushdb
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def stats
|
132
|
+
@data.info
|
133
|
+
end
|
134
|
+
|
135
|
+
# Force client reconnection, useful Unicorn deployed apps.
|
136
|
+
def reconnect
|
137
|
+
@data.reconnect
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
def write_entry(key, entry, options)
|
142
|
+
method = options && options[:unless_exist] ? :setnx : :set
|
143
|
+
@data.send method, key, entry, options
|
144
|
+
rescue Errno::ECONNREFUSED => e
|
145
|
+
false
|
146
|
+
end
|
147
|
+
|
148
|
+
def read_entry(key, options)
|
149
|
+
entry = @data.get key, options
|
150
|
+
if entry
|
151
|
+
entry.is_a?(ActiveSupport::Cache::Entry) ? entry : ActiveSupport::Cache::Entry.new(entry)
|
152
|
+
end
|
153
|
+
rescue Errno::ECONNREFUSED => e
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Implement the ActiveSupport::Cache#delete_entry
|
159
|
+
#
|
160
|
+
# It's really needed and use
|
161
|
+
#
|
162
|
+
def delete_entry(key, options)
|
163
|
+
@data.del key
|
164
|
+
rescue Errno::ECONNREFUSED => e
|
165
|
+
false
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Add the namespace defined in the options to a pattern designed to match keys.
|
170
|
+
#
|
171
|
+
# This implementation is __different__ than ActiveSupport:
|
172
|
+
# __it doesn't accept Regular expressions__, because the Redis matcher is designed
|
173
|
+
# only for strings with wildcards.
|
174
|
+
def key_matcher(pattern, options)
|
175
|
+
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
|
176
|
+
if prefix
|
177
|
+
raise "Regexps aren't supported, please use string with wildcards." if pattern.is_a?(Regexp)
|
178
|
+
"#{prefix}:#{pattern}"
|
179
|
+
else
|
180
|
+
pattern
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
class Redis
|
5
|
+
module SlaveRead
|
6
|
+
module Interface
|
7
|
+
class Base
|
8
|
+
attr_accessor :master, :slaves, :nodes, :read_master, :all
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def slave(commands)
|
12
|
+
commands.each do |method|
|
13
|
+
define_method(method) do |*args|
|
14
|
+
next_node.send(method, *args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def master(commands)
|
20
|
+
commands.each do |method|
|
21
|
+
define_method(method) do |*args|
|
22
|
+
master.send(method, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def all(commands)
|
28
|
+
commands.each do |method|
|
29
|
+
define_method(method) do |*args|
|
30
|
+
@all.each do |node|
|
31
|
+
node.send(method, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(options = {})
|
39
|
+
@block_exec_mutex = Mutex.new
|
40
|
+
@round_robin_mutex = Mutex.new
|
41
|
+
@master = options[:master] || raise("Must specify a master")
|
42
|
+
@slaves = options[:slaves] || []
|
43
|
+
@read_master = options[:read_master].nil? && true || options[:read_master]
|
44
|
+
@all = slaves + [@master]
|
45
|
+
@nodes = slaves.dup
|
46
|
+
@nodes.unshift @master if @read_master
|
47
|
+
@index = 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_missing(method, *args)
|
51
|
+
if master.respond_to?(method)
|
52
|
+
define_method(method) do |*_args|
|
53
|
+
@master.send(method, *_args)
|
54
|
+
end
|
55
|
+
send(method, *args)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def pipelined(*args, &block)
|
62
|
+
@block_exec_mutex.synchronize do
|
63
|
+
@locked_node = @master
|
64
|
+
@master.send(:pipelined, *args, &block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def exec(*args)
|
69
|
+
@block_exec_mutex.synchronize do
|
70
|
+
@locked_node = nil
|
71
|
+
@master.send(:exec, *args)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def discard(*args)
|
76
|
+
@block_exec_mutex.synchronize do
|
77
|
+
@locked_node = nil
|
78
|
+
@master.send(:discard, *args)
|
79
|
+
@locked_node = nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def multi(*args, &block)
|
84
|
+
@block_exec_mutex.synchronize do
|
85
|
+
@locked_node = @master
|
86
|
+
@master.send(:multi, *args, &block)
|
87
|
+
@locked_node = nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def next_node
|
94
|
+
@round_robin_mutex.synchronize do
|
95
|
+
if @locked_node
|
96
|
+
@locked_node
|
97
|
+
else
|
98
|
+
@index = (@index + 1) % @nodes.length
|
99
|
+
@nodes[@index]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Redis
|
2
|
+
module SlaveRead
|
3
|
+
module Interface
|
4
|
+
class Hiredis < Base
|
5
|
+
slave %w(
|
6
|
+
bitcount dbsize debug dump echo exists get getbit getrange hexists hget hgetall hkeys hlen hmget hvals info keys lastsave lindex llen mget object ping pttl
|
7
|
+
randomkey scard sismember smembers srandmember strlen sunion time ttl type zcard zcount zrange zrangebyscore zrank zrevrank zrevrangebyscore zrevrank zscore
|
8
|
+
)
|
9
|
+
|
10
|
+
master %w(
|
11
|
+
append auth bgrewriteaof bgsave bitop blpop brpop brpoplpush client config debug debug decr decrby del eval evalsha expire expireat flushall
|
12
|
+
flushdb getset hdel hincrby hincrbyfloat hmset hset hsetnx incr incrby incrbyfloat linsert lpop lpush lpushx lrange lrem lset ltrim migrate monitor move mset
|
13
|
+
msetnx persist pexpire pexpireat psetex psubscribe publish punsubscribe quit rename renamenx restore rpop rpoplpush rpush rpushx sadd save script exists
|
14
|
+
script flush script kill script load sdiff sdiffstore select set setbit setex setnx setrange shutdown sinter sinterstore slaveof slowlog smove sort spop srem
|
15
|
+
subscribe sunionstore sync unsubscribe unwatch watch zadd zincrby zinterstore zrem zremrangebyrank zremrangebyscore zrevrange zunionstore
|
16
|
+
)
|
17
|
+
|
18
|
+
all %w(select)
|
19
|
+
|
20
|
+
%w(db= connect disconnect reconnect).each do |method|
|
21
|
+
define_method(method) do |*args|
|
22
|
+
all.each do |node|
|
23
|
+
node.client.send(method, *args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'redis-slave-read/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "redis-slave-read"
|
8
|
+
gem.version = Redis::SlaveRead::VERSION
|
9
|
+
gem.authors = ["Chris Heald"]
|
10
|
+
gem.email = ["cheald@gmail.com"]
|
11
|
+
gem.description = %q{Provides load balancing of reads in a cluster of Redis replicas}
|
12
|
+
gem.summary = %q{Provides load balancing of reads in a cluster of Redis replicas}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "redis"
|
21
|
+
gem.add_development_dependency "redis"
|
22
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Redis::SlaveRead::Interface::Hiredis do
|
4
|
+
let(:master) { Redis.new }
|
5
|
+
let(:slaves) { [Redis.new, Redis.new] }
|
6
|
+
|
7
|
+
subject { described_class.new master: master, slaves: slaves }
|
8
|
+
|
9
|
+
it "should distribute reads between all available nodes" do
|
10
|
+
master.should_receive(:get).once
|
11
|
+
slaves[0].should_receive(:get).once
|
12
|
+
slaves[1].should_receive(:get).once
|
13
|
+
|
14
|
+
3.times { subject.get "foo" }
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should always send non-reads to the master" do
|
18
|
+
master.should_receive(:set).exactly(3).times
|
19
|
+
|
20
|
+
3.times { subject.set "foo", "bar" }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when read_master is false" do
|
24
|
+
subject { described_class.new master: master, slaves: slaves, read_master: false }
|
25
|
+
|
26
|
+
it "should distribute reads between all available slaves" do
|
27
|
+
master.should_receive(:get).never
|
28
|
+
slaves[1].should_receive(:get).twice
|
29
|
+
slaves[0].should_receive(:get).once
|
30
|
+
|
31
|
+
3.times { subject.get "foo" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when in a multi block" do
|
36
|
+
it "sends all commands to the master" do
|
37
|
+
master.should_receive(:get).twice
|
38
|
+
|
39
|
+
subject.multi do
|
40
|
+
2.times { subject.get "foo" }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when in a pipelined block" do
|
46
|
+
it "sends all commands to the master" do
|
47
|
+
master.should_receive(:get).twice
|
48
|
+
|
49
|
+
subject.pipelined do
|
50
|
+
2.times { subject.get "foo" }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "commands that distribute to all nodes" do
|
56
|
+
it "should distribute to each node" do
|
57
|
+
master.should_receive(:select).once
|
58
|
+
slaves.each {|slave| slave.should_receive(:select).once }
|
59
|
+
subject.send(:select)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should set the DB on each node" do
|
63
|
+
subject.select 4
|
64
|
+
master.client.db.should == 4
|
65
|
+
slaves[0].client.db.should == 4
|
66
|
+
slaves[1].client.db.should == 4
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should disconnect each client" do
|
70
|
+
subject.disconnect
|
71
|
+
!!master.client.connected?.should == false
|
72
|
+
!!slaves[0].client.connected?.should == false
|
73
|
+
!!slaves[1].client.connected?.should == false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis-slave-read
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: '0.1'
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Heald
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
type: :runtime
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
name: redis
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
type: :development
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
name: redis
|
46
|
+
description: Provides load balancing of reads in a cluster of Redis replicas
|
47
|
+
email:
|
48
|
+
- cheald@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/activesupport/lib/active_support/cache/redis_slave_read_cache.rb
|
59
|
+
- lib/redis-slave-read.rb
|
60
|
+
- lib/redis-slave-read/interface/base.rb
|
61
|
+
- lib/redis-slave-read/interface/hiredis.rb
|
62
|
+
- lib/redis-slave-read/version.rb
|
63
|
+
- redis-slave-read.gemspec
|
64
|
+
- spec/interface/hiredis_spec.rb
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
homepage: ''
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.24
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Provides load balancing of reads in a cluster of Redis replicas
|
90
|
+
test_files:
|
91
|
+
- spec/interface/hiredis_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
has_rdoc:
|