yam-redis-with-retries 2.2.2.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/CHANGELOG.md +53 -0
- data/LICENSE +20 -0
- data/README.md +208 -0
- data/Rakefile +277 -0
- data/benchmarking/logging.rb +62 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/thread_safety.rb +38 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +31 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis.rb +1166 -0
- data/lib/redis/client.rb +265 -0
- data/lib/redis/compat.rb +21 -0
- data/lib/redis/connection.rb +9 -0
- data/lib/redis/connection/command_helper.rb +45 -0
- data/lib/redis/connection/hiredis.rb +49 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +135 -0
- data/lib/redis/connection/synchrony.rb +129 -0
- data/lib/redis/distributed.rb +694 -0
- data/lib/redis/hash_ring.rb +131 -0
- data/lib/redis/pipeline.rb +34 -0
- data/lib/redis/retry.rb +128 -0
- data/lib/redis/subscribe.rb +94 -0
- data/lib/redis/version.rb +3 -0
- data/test/commands_on_hashes_test.rb +20 -0
- data/test/commands_on_lists_test.rb +60 -0
- data/test/commands_on_sets_test.rb +78 -0
- data/test/commands_on_sorted_sets_test.rb +109 -0
- data/test/commands_on_strings_test.rb +80 -0
- data/test/commands_on_value_types_test.rb +88 -0
- data/test/connection_handling_test.rb +88 -0
- data/test/distributed_blocking_commands_test.rb +53 -0
- data/test/distributed_commands_on_hashes_test.rb +12 -0
- data/test/distributed_commands_on_lists_test.rb +24 -0
- data/test/distributed_commands_on_sets_test.rb +85 -0
- data/test/distributed_commands_on_strings_test.rb +50 -0
- data/test/distributed_commands_on_value_types_test.rb +73 -0
- data/test/distributed_commands_requiring_clustering_test.rb +148 -0
- data/test/distributed_connection_handling_test.rb +25 -0
- data/test/distributed_internals_test.rb +27 -0
- data/test/distributed_key_tags_test.rb +53 -0
- data/test/distributed_persistence_control_commands_test.rb +24 -0
- data/test/distributed_publish_subscribe_test.rb +101 -0
- data/test/distributed_remote_server_control_commands_test.rb +43 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +59 -0
- data/test/distributed_transactions_test.rb +34 -0
- data/test/encoding_test.rb +16 -0
- data/test/error_replies_test.rb +53 -0
- data/test/helper.rb +145 -0
- data/test/internals_test.rb +163 -0
- data/test/lint/hashes.rb +126 -0
- data/test/lint/internals.rb +37 -0
- data/test/lint/lists.rb +93 -0
- data/test/lint/sets.rb +66 -0
- data/test/lint/sorted_sets.rb +167 -0
- data/test/lint/strings.rb +137 -0
- data/test/lint/value_types.rb +84 -0
- data/test/persistence_control_commands_test.rb +22 -0
- data/test/pipelining_commands_test.rb +123 -0
- data/test/publish_subscribe_test.rb +158 -0
- data/test/redis_mock.rb +80 -0
- data/test/remote_server_control_commands_test.rb +82 -0
- data/test/retry_test.rb +225 -0
- data/test/sorting_test.rb +44 -0
- data/test/synchrony_driver.rb +57 -0
- data/test/test.conf +8 -0
- data/test/thread_safety_test.rb +30 -0
- data/test/transactions_test.rb +100 -0
- data/test/unknown_commands_test.rb +14 -0
- data/test/url_param_test.rb +60 -0
- metadata +215 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
require "redis/connection/command_helper"
|
2
|
+
require "redis/connection/registry"
|
3
|
+
require "em-synchrony"
|
4
|
+
require "hiredis/reader"
|
5
|
+
|
6
|
+
class Redis
|
7
|
+
module Connection
|
8
|
+
class RedisClient < EventMachine::Connection
|
9
|
+
include EventMachine::Deferrable
|
10
|
+
|
11
|
+
def post_init
|
12
|
+
@req = nil
|
13
|
+
@connected = false
|
14
|
+
@reader = ::Hiredis::Reader.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def connection_completed
|
18
|
+
@connected = true
|
19
|
+
succeed
|
20
|
+
end
|
21
|
+
|
22
|
+
def connected?
|
23
|
+
@connected
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive_data(data)
|
27
|
+
@reader.feed(data)
|
28
|
+
|
29
|
+
begin
|
30
|
+
until (reply = @reader.gets) == false
|
31
|
+
@req.succeed [:reply, reply]
|
32
|
+
end
|
33
|
+
rescue RuntimeError => err
|
34
|
+
@req.fail [:error, ::Redis::ProtocolError.new(err.message)]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def read
|
39
|
+
@req = EventMachine::DefaultDeferrable.new
|
40
|
+
EventMachine::Synchrony.sync @req
|
41
|
+
end
|
42
|
+
|
43
|
+
def send(data)
|
44
|
+
callback { send_data data }
|
45
|
+
end
|
46
|
+
|
47
|
+
def unbind
|
48
|
+
@connected = false
|
49
|
+
if @req
|
50
|
+
@req.fail [:error, Errno::ECONNRESET]
|
51
|
+
@req = nil
|
52
|
+
else
|
53
|
+
fail
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Synchrony
|
59
|
+
include Redis::Connection::CommandHelper
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
@timeout = 5_000_000
|
63
|
+
@connection = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def connected?
|
67
|
+
@connection && @connection.connected?
|
68
|
+
end
|
69
|
+
|
70
|
+
def timeout=(usecs)
|
71
|
+
@timeout = usecs
|
72
|
+
end
|
73
|
+
|
74
|
+
def connect(host, port, timeout)
|
75
|
+
conn = EventMachine.connect(host, port, RedisClient) do |c|
|
76
|
+
c.pending_connect_timeout = [Float(timeout / 1_000_000), 0.1].max
|
77
|
+
end
|
78
|
+
|
79
|
+
setup_connect_callbacks(conn, Fiber.current)
|
80
|
+
end
|
81
|
+
|
82
|
+
def connect_unix(path, timeout)
|
83
|
+
conn = EventMachine.connect_unix_domain(path, RedisClient)
|
84
|
+
setup_connect_callbacks(conn, Fiber.current)
|
85
|
+
end
|
86
|
+
|
87
|
+
def disconnect
|
88
|
+
@connection.close_connection
|
89
|
+
@connection = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def write(command)
|
93
|
+
@connection.send(build_command(command))
|
94
|
+
end
|
95
|
+
|
96
|
+
def read
|
97
|
+
type, payload = @connection.read
|
98
|
+
|
99
|
+
if type == :reply
|
100
|
+
payload
|
101
|
+
elsif type == :error
|
102
|
+
raise payload
|
103
|
+
else
|
104
|
+
raise "Unknown type #{type.inspect}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def setup_connect_callbacks(conn, f)
|
111
|
+
conn.callback do
|
112
|
+
@connection = conn
|
113
|
+
f.resume conn
|
114
|
+
end
|
115
|
+
|
116
|
+
conn.errback do
|
117
|
+
@connection = conn
|
118
|
+
f.resume :refused
|
119
|
+
end
|
120
|
+
|
121
|
+
r = Fiber.yield
|
122
|
+
raise Errno::ECONNREFUSED if r == :refused
|
123
|
+
r
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
Redis::Connection.drivers << Redis::Connection::Synchrony
|
@@ -0,0 +1,694 @@
|
|
1
|
+
require "redis/hash_ring"
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class Distributed
|
5
|
+
|
6
|
+
class CannotDistribute < RuntimeError
|
7
|
+
def initialize(command)
|
8
|
+
@command = command
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :ring
|
17
|
+
|
18
|
+
def initialize(urls, options={})
|
19
|
+
@tag = options.delete(:tag) || /^\{(.+?)\}/
|
20
|
+
@default_options = options
|
21
|
+
@ring = HashRing.new urls.map { |url| Redis.connect(options.merge(:url => url)) }
|
22
|
+
@subscribed_node = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def node_for(key)
|
26
|
+
@ring.get_node(key_tag(key.to_s) || key.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def nodes
|
30
|
+
@ring.nodes
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_node(url)
|
34
|
+
@ring.add_node Redis.connect(@default_options.merge(:url => url))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Close the connection.
|
38
|
+
def quit
|
39
|
+
on_each_node :quit
|
40
|
+
end
|
41
|
+
|
42
|
+
# Change the selected database for the current connection.
|
43
|
+
def select(db)
|
44
|
+
on_each_node :select, db
|
45
|
+
end
|
46
|
+
|
47
|
+
# Ping the server.
|
48
|
+
def ping
|
49
|
+
on_each_node :ping
|
50
|
+
end
|
51
|
+
|
52
|
+
# Remove all keys from all databases.
|
53
|
+
def flushall
|
54
|
+
on_each_node :flushall
|
55
|
+
end
|
56
|
+
|
57
|
+
# Determine if a key exists.
|
58
|
+
def exists(key)
|
59
|
+
node_for(key).exists(key)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Delete a key.
|
63
|
+
def del(*args)
|
64
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
65
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
66
|
+
sum + node.del(*keys)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Determine the type stored at key.
|
71
|
+
def type(key)
|
72
|
+
node_for(key).type(key)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Find all keys matching the given pattern.
|
76
|
+
def keys(glob = "*")
|
77
|
+
on_each_node(:keys, glob).flatten
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return a random key from the keyspace.
|
81
|
+
def randomkey
|
82
|
+
raise CannotDistribute, :randomkey
|
83
|
+
end
|
84
|
+
|
85
|
+
# Rename a key.
|
86
|
+
def rename(old_name, new_name)
|
87
|
+
ensure_same_node(:rename, old_name, new_name) do |node|
|
88
|
+
node.rename(old_name, new_name)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Rename a key, only if the new key does not exist.
|
93
|
+
def renamenx(old_name, new_name)
|
94
|
+
ensure_same_node(:renamenx, old_name, new_name) do |node|
|
95
|
+
node.renamenx(old_name, new_name)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return the number of keys in the selected database.
|
100
|
+
def dbsize
|
101
|
+
on_each_node :dbsize
|
102
|
+
end
|
103
|
+
|
104
|
+
# Set a key's time to live in seconds.
|
105
|
+
def expire(key, seconds)
|
106
|
+
node_for(key).expire(key, seconds)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Set the expiration for a key as a UNIX timestamp.
|
110
|
+
def expireat(key, unix_time)
|
111
|
+
node_for(key).expireat(key, unix_time)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Remove the expiration from a key.
|
115
|
+
def persist(key)
|
116
|
+
node_for(key).persist(key)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Get the time to live for a key.
|
120
|
+
def ttl(key)
|
121
|
+
node_for(key).ttl(key)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Move a key to another database.
|
125
|
+
def move(key, db)
|
126
|
+
node_for(key).move(key, db)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Remove all keys from the current database.
|
130
|
+
def flushdb
|
131
|
+
on_each_node :flushdb
|
132
|
+
end
|
133
|
+
|
134
|
+
# Set the string value of a key.
|
135
|
+
def set(key, value)
|
136
|
+
node_for(key).set(key, value)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Sets or clears the bit at offset in the string value stored at key.
|
140
|
+
def setbit(key, offset, value)
|
141
|
+
node_for(key).setbit(key, offset, value)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Overwrite part of a string at key starting at the specified offset.
|
145
|
+
def setrange(key, offset, value)
|
146
|
+
node_for(key).setrange(key, offset, value)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Set the value and expiration of a key.
|
150
|
+
def setex(key, ttl, value)
|
151
|
+
node_for(key).setex(key, ttl, value)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Get the value of a key.
|
155
|
+
def get(key)
|
156
|
+
node_for(key).get(key)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the bit value at offset in the string value stored at key.
|
160
|
+
def getbit(key, offset)
|
161
|
+
node_for(key).getbit(key, offset)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Get a substring of the string stored at a key.
|
165
|
+
def getrange(key, start, stop)
|
166
|
+
node_for(key).getrange(key, start, stop)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Set the string value of a key and return its old value.
|
170
|
+
def getset(key, value)
|
171
|
+
node_for(key).getset(key, value)
|
172
|
+
end
|
173
|
+
|
174
|
+
def [](key)
|
175
|
+
get(key)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Append a value to a key.
|
179
|
+
def append(key, value)
|
180
|
+
node_for(key).append(key, value)
|
181
|
+
end
|
182
|
+
|
183
|
+
def substr(key, start, stop)
|
184
|
+
node_for(key).substr(key, start, stop)
|
185
|
+
end
|
186
|
+
|
187
|
+
def []=(key,value)
|
188
|
+
set(key, value)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Get the values of all the given keys.
|
192
|
+
def mget(*keys)
|
193
|
+
raise CannotDistribute, :mget
|
194
|
+
end
|
195
|
+
|
196
|
+
def mapped_mget(*keys)
|
197
|
+
raise CannotDistribute, :mapped_mget
|
198
|
+
end
|
199
|
+
|
200
|
+
# Set the value of a key, only if the key does not exist.
|
201
|
+
def setnx(key, value)
|
202
|
+
node_for(key).setnx(key, value)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Set multiple keys to multiple values.
|
206
|
+
def mset(*args)
|
207
|
+
raise CannotDistribute, :mset
|
208
|
+
end
|
209
|
+
|
210
|
+
def mapped_mset(hash)
|
211
|
+
mset(*hash.to_a.flatten)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Set multiple keys to multiple values, only if none of the keys exist.
|
215
|
+
def msetnx(*args)
|
216
|
+
raise CannotDistribute, :msetnx
|
217
|
+
end
|
218
|
+
|
219
|
+
def mapped_msetnx(hash)
|
220
|
+
raise CannotDistribute, :mapped_msetnx
|
221
|
+
end
|
222
|
+
|
223
|
+
# Increment the integer value of a key by one.
|
224
|
+
def incr(key)
|
225
|
+
node_for(key).incr(key)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Increment the integer value of a key by the given number.
|
229
|
+
def incrby(key, increment)
|
230
|
+
node_for(key).incrby(key, increment)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Decrement the integer value of a key by one.
|
234
|
+
def decr(key)
|
235
|
+
node_for(key).decr(key)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Decrement the integer value of a key by the given number.
|
239
|
+
def decrby(key, decrement)
|
240
|
+
node_for(key).decrby(key, decrement)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Append a value to a list.
|
244
|
+
def rpush(key, value)
|
245
|
+
node_for(key).rpush(key, value)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Prepend a value to a list.
|
249
|
+
def lpush(key, value)
|
250
|
+
node_for(key).lpush(key, value)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Get the length of a list.
|
254
|
+
def llen(key)
|
255
|
+
node_for(key).llen(key)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Get a range of elements from a list.
|
259
|
+
def lrange(key, start, stop)
|
260
|
+
node_for(key).lrange(key, start, stop)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Trim a list to the specified range.
|
264
|
+
def ltrim(key, start, stop)
|
265
|
+
node_for(key).ltrim(key, start, stop)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Get an element from a list by its index.
|
269
|
+
def lindex(key, index)
|
270
|
+
node_for(key).lindex(key, index)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Set the value of an element in a list by its index.
|
274
|
+
def lset(key, index, value)
|
275
|
+
node_for(key).lset(key, index, value)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Remove elements from a list.
|
279
|
+
def lrem(key, count, value)
|
280
|
+
node_for(key).lrem(key, count, value)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Remove and get the first element in a list.
|
284
|
+
def lpop(key)
|
285
|
+
node_for(key).lpop(key)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Remove and get the last element in a list.
|
289
|
+
def rpop(key)
|
290
|
+
node_for(key).rpop(key)
|
291
|
+
end
|
292
|
+
|
293
|
+
# Remove the last element in a list, append it to another list and return
|
294
|
+
# it.
|
295
|
+
def rpoplpush(source, destination)
|
296
|
+
ensure_same_node(:rpoplpush, source, destination) do |node|
|
297
|
+
node.rpoplpush(source, destination)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Remove and get the first element in a list, or block until one is
|
302
|
+
# available.
|
303
|
+
def blpop(key, timeout)
|
304
|
+
node_for(key).blpop(key, timeout)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Remove and get the last element in a list, or block until one is
|
308
|
+
# available.
|
309
|
+
def brpop(key, timeout)
|
310
|
+
node_for(key).brpop(key, timeout)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Pop a value from a list, push it to another list and return it; or block
|
314
|
+
# until one is available.
|
315
|
+
def brpoplpush(source, destination, timeout)
|
316
|
+
ensure_same_node(:brpoplpush, source, destination) do |node|
|
317
|
+
node.brpoplpush(source, destination, timeout)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Add a member to a set.
|
322
|
+
def sadd(key, value)
|
323
|
+
node_for(key).sadd(key, value)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Remove a member from a set.
|
327
|
+
def srem(key, value)
|
328
|
+
node_for(key).srem(key, value)
|
329
|
+
end
|
330
|
+
|
331
|
+
# Remove and return a random member from a set.
|
332
|
+
def spop(key)
|
333
|
+
node_for(key).spop(key)
|
334
|
+
end
|
335
|
+
|
336
|
+
# Move a member from one set to another.
|
337
|
+
def smove(source, destination, member)
|
338
|
+
ensure_same_node(:smove, source, destination) do |node|
|
339
|
+
node.smove(source, destination, member)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Get the number of members in a set.
|
344
|
+
def scard(key)
|
345
|
+
node_for(key).scard(key)
|
346
|
+
end
|
347
|
+
|
348
|
+
# Determine if a given value is a member of a set.
|
349
|
+
def sismember(key, member)
|
350
|
+
node_for(key).sismember(key, member)
|
351
|
+
end
|
352
|
+
|
353
|
+
# Intersect multiple sets.
|
354
|
+
def sinter(*keys)
|
355
|
+
ensure_same_node(:sinter, *keys) do |node|
|
356
|
+
node.sinter(*keys)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# Intersect multiple sets and store the resulting set in a key.
|
361
|
+
def sinterstore(destination, *keys)
|
362
|
+
ensure_same_node(:sinterstore, destination, *keys) do |node|
|
363
|
+
node.sinterstore(destination, *keys)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Add multiple sets.
|
368
|
+
def sunion(*keys)
|
369
|
+
ensure_same_node(:sunion, *keys) do |node|
|
370
|
+
node.sunion(*keys)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
# Add multiple sets and store the resulting set in a key.
|
375
|
+
def sunionstore(destination, *keys)
|
376
|
+
ensure_same_node(:sunionstore, destination, *keys) do |node|
|
377
|
+
node.sunionstore(destination, *keys)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Subtract multiple sets.
|
382
|
+
def sdiff(*keys)
|
383
|
+
ensure_same_node(:sdiff, *keys) do |node|
|
384
|
+
node.sdiff(*keys)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# Subtract multiple sets and store the resulting set in a key.
|
389
|
+
def sdiffstore(destination, *keys)
|
390
|
+
ensure_same_node(:sdiffstore, destination, *keys) do |node|
|
391
|
+
node.sdiffstore(destination, *keys)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Get all the members in a set.
|
396
|
+
def smembers(key)
|
397
|
+
node_for(key).smembers(key)
|
398
|
+
end
|
399
|
+
|
400
|
+
# Get a random member from a set.
|
401
|
+
def srandmember(key)
|
402
|
+
node_for(key).srandmember(key)
|
403
|
+
end
|
404
|
+
|
405
|
+
# Add a member to a sorted set, or update its score if it already exists.
|
406
|
+
def zadd(key, score, member)
|
407
|
+
node_for(key).zadd(key, score, member)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Remove a member from a sorted set.
|
411
|
+
def zrem(key, member)
|
412
|
+
node_for(key).zrem(key, member)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Increment the score of a member in a sorted set.
|
416
|
+
def zincrby(key, increment, member)
|
417
|
+
node_for(key).zincrby(key, increment, member)
|
418
|
+
end
|
419
|
+
|
420
|
+
# Return a range of members in a sorted set, by index.
|
421
|
+
def zrange(key, start, stop, options = {})
|
422
|
+
node_for(key).zrange(key, start, stop, options)
|
423
|
+
end
|
424
|
+
|
425
|
+
# Determine the index of a member in a sorted set.
|
426
|
+
def zrank(key, member)
|
427
|
+
node_for(key).zrank(key, member)
|
428
|
+
end
|
429
|
+
|
430
|
+
# Determine the index of a member in a sorted set, with scores ordered from
|
431
|
+
# high to low.
|
432
|
+
def zrevrank(key, member)
|
433
|
+
node_for(key).zrevrank(key, member)
|
434
|
+
end
|
435
|
+
|
436
|
+
# Return a range of members in a sorted set, by index, with scores ordered
|
437
|
+
# from high to low.
|
438
|
+
def zrevrange(key, start, stop, options = {})
|
439
|
+
node_for(key).zrevrange(key, start, stop, options)
|
440
|
+
end
|
441
|
+
|
442
|
+
# Remove all members in a sorted set within the given scores.
|
443
|
+
def zremrangebyscore(key, min, max)
|
444
|
+
node_for(key).zremrangebyscore(key, min, max)
|
445
|
+
end
|
446
|
+
|
447
|
+
# Remove all members in a sorted set within the given indexes.
|
448
|
+
def zremrangebyrank(key, start, stop)
|
449
|
+
node_for(key).zremrangebyrank(key, start, stop)
|
450
|
+
end
|
451
|
+
|
452
|
+
# Return a range of members in a sorted set, by score.
|
453
|
+
def zrangebyscore(key, min, max, options = {})
|
454
|
+
node_for(key).zrangebyscore(key, min, max, options)
|
455
|
+
end
|
456
|
+
|
457
|
+
# Return a range of members in a sorted set, by score, with scores ordered
|
458
|
+
# from high to low.
|
459
|
+
def zrevrangebyscore(key, max, min, options = {})
|
460
|
+
node_for(key).zrevrangebyscore(key, max, min, options)
|
461
|
+
end
|
462
|
+
|
463
|
+
# Get the number of members in a sorted set.
|
464
|
+
def zcard(key)
|
465
|
+
node_for(key).zcard(key)
|
466
|
+
end
|
467
|
+
|
468
|
+
# Get the score associated with the given member in a sorted set.
|
469
|
+
def zscore(key, member)
|
470
|
+
node_for(key).zscore(key, member)
|
471
|
+
end
|
472
|
+
|
473
|
+
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
474
|
+
# key.
|
475
|
+
def zinterstore(destination, keys, options = {})
|
476
|
+
ensure_same_node(:zinterstore, destination, *keys) do |node|
|
477
|
+
node.zinterstore(destination, keys, options)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
482
|
+
def zunionstore(destination, keys, options = {})
|
483
|
+
ensure_same_node(:zunionstore, destination, *keys) do |node|
|
484
|
+
node.zunionstore(destination, keys, options)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
# Set the string value of a hash field.
|
489
|
+
def hset(key, field, value)
|
490
|
+
node_for(key).hset(key, field, value)
|
491
|
+
end
|
492
|
+
|
493
|
+
# Set the value of a hash field, only if the field does not exist.
|
494
|
+
def hsetnx(key, field, value)
|
495
|
+
node_for(key).hsetnx(key, field, value)
|
496
|
+
end
|
497
|
+
|
498
|
+
# Get the value of a hash field.
|
499
|
+
def hget(key, field)
|
500
|
+
node_for(key).hget(key, field)
|
501
|
+
end
|
502
|
+
|
503
|
+
# Delete a hash field.
|
504
|
+
def hdel(key, field)
|
505
|
+
node_for(key).hdel(key, field)
|
506
|
+
end
|
507
|
+
|
508
|
+
# Determine if a hash field exists.
|
509
|
+
def hexists(key, field)
|
510
|
+
node_for(key).hexists(key, field)
|
511
|
+
end
|
512
|
+
|
513
|
+
# Get the number of fields in a hash.
|
514
|
+
def hlen(key)
|
515
|
+
node_for(key).hlen(key)
|
516
|
+
end
|
517
|
+
|
518
|
+
# Get all the fields in a hash.
|
519
|
+
def hkeys(key)
|
520
|
+
node_for(key).hkeys(key)
|
521
|
+
end
|
522
|
+
|
523
|
+
# Get all the values in a hash.
|
524
|
+
def hvals(key)
|
525
|
+
node_for(key).hvals(key)
|
526
|
+
end
|
527
|
+
|
528
|
+
# Get all the fields and values in a hash.
|
529
|
+
def hgetall(key)
|
530
|
+
node_for(key).hgetall(key)
|
531
|
+
end
|
532
|
+
|
533
|
+
# Set multiple hash fields to multiple values.
|
534
|
+
def hmset(key, *attrs)
|
535
|
+
node_for(key).hmset(key, *attrs)
|
536
|
+
end
|
537
|
+
|
538
|
+
def mapped_hmset(key, hash)
|
539
|
+
node_for(key).hmset(key, *hash.to_a.flatten)
|
540
|
+
end
|
541
|
+
|
542
|
+
# Get the values of all the given hash fields.
|
543
|
+
def hmget(key, *fields)
|
544
|
+
node_for(key).hmget(key, *fields)
|
545
|
+
end
|
546
|
+
|
547
|
+
def mapped_hmget(key, *fields)
|
548
|
+
Hash[*fields.zip(hmget(key, *fields)).flatten]
|
549
|
+
end
|
550
|
+
|
551
|
+
# Increment the integer value of a hash field by the given number.
|
552
|
+
def hincrby(key, field, increment)
|
553
|
+
node_for(key).hincrby(key, field, increment)
|
554
|
+
end
|
555
|
+
|
556
|
+
# Sort the elements in a list, set or sorted set.
|
557
|
+
def sort(key, options = {})
|
558
|
+
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
559
|
+
keys.delete('#')
|
560
|
+
ensure_same_node(:sort, *keys) do |node|
|
561
|
+
node.sort(key, options)
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
# Mark the start of a transaction block.
|
566
|
+
def multi
|
567
|
+
raise CannotDistribute, :multi
|
568
|
+
end
|
569
|
+
|
570
|
+
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
571
|
+
def watch(*keys)
|
572
|
+
raise CannotDistribute, :watch
|
573
|
+
end
|
574
|
+
|
575
|
+
# Forget about all watched keys.
|
576
|
+
def unwatch
|
577
|
+
raise CannotDistribute, :unwatch
|
578
|
+
end
|
579
|
+
|
580
|
+
# Execute all commands issued after MULTI.
|
581
|
+
def exec
|
582
|
+
raise CannotDistribute, :exec
|
583
|
+
end
|
584
|
+
|
585
|
+
# Discard all commands issued after MULTI.
|
586
|
+
def discard
|
587
|
+
raise CannotDistribute, :discard
|
588
|
+
end
|
589
|
+
|
590
|
+
# Post a message to a channel.
|
591
|
+
def publish(channel, message)
|
592
|
+
node_for(channel).publish(channel, message)
|
593
|
+
end
|
594
|
+
|
595
|
+
def subscribed?
|
596
|
+
!! @subscribed_node
|
597
|
+
end
|
598
|
+
|
599
|
+
# Stop listening for messages posted to the given channels.
|
600
|
+
def unsubscribe(*channels)
|
601
|
+
raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
|
602
|
+
@subscribed_node.unsubscribe(*channels)
|
603
|
+
end
|
604
|
+
|
605
|
+
# Listen for messages published to the given channels.
|
606
|
+
def subscribe(channel, *channels, &block)
|
607
|
+
if channels.empty?
|
608
|
+
@subscribed_node = node_for(channel)
|
609
|
+
@subscribed_node.subscribe(channel, &block)
|
610
|
+
else
|
611
|
+
ensure_same_node(:subscribe, channel, *channels) do |node|
|
612
|
+
@subscribed_node = node
|
613
|
+
node.subscribe(channel, *channels, &block)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
# Stop listening for messages posted to channels matching the given
|
619
|
+
# patterns.
|
620
|
+
def punsubscribe(*channels)
|
621
|
+
raise NotImplementedError
|
622
|
+
end
|
623
|
+
|
624
|
+
# Listen for messages published to channels matching the given patterns.
|
625
|
+
def psubscribe(*channels, &block)
|
626
|
+
raise NotImplementedError
|
627
|
+
end
|
628
|
+
|
629
|
+
# Synchronously save the dataset to disk.
|
630
|
+
def save
|
631
|
+
on_each_node :save
|
632
|
+
end
|
633
|
+
|
634
|
+
# Asynchronously save the dataset to disk.
|
635
|
+
def bgsave
|
636
|
+
on_each_node :bgsave
|
637
|
+
end
|
638
|
+
|
639
|
+
# Get the UNIX time stamp of the last successful save to disk.
|
640
|
+
def lastsave
|
641
|
+
on_each_node :lastsave
|
642
|
+
end
|
643
|
+
|
644
|
+
# Get information and statistics about the server.
|
645
|
+
def info(cmd = nil)
|
646
|
+
on_each_node :info, cmd
|
647
|
+
end
|
648
|
+
|
649
|
+
# Listen for all requests received by the server in real time.
|
650
|
+
def monitor
|
651
|
+
raise NotImplementedError
|
652
|
+
end
|
653
|
+
|
654
|
+
# Echo the given string.
|
655
|
+
def echo(value)
|
656
|
+
on_each_node :echo, value
|
657
|
+
end
|
658
|
+
|
659
|
+
def pipelined
|
660
|
+
raise CannotDistribute, :pipelined
|
661
|
+
end
|
662
|
+
|
663
|
+
def inspect
|
664
|
+
node_info = nodes.map do |node|
|
665
|
+
"#{node.id} (Redis v#{node.info['redis_version']})"
|
666
|
+
end
|
667
|
+
"#<Redis client v#{Redis::VERSION} connected to #{node_info.join(', ')}>"
|
668
|
+
end
|
669
|
+
|
670
|
+
protected
|
671
|
+
|
672
|
+
def on_each_node(command, *args)
|
673
|
+
nodes.map do |node|
|
674
|
+
node.send(command, *args)
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
def node_index_for(key)
|
679
|
+
nodes.index(node_for(key))
|
680
|
+
end
|
681
|
+
|
682
|
+
def key_tag(key)
|
683
|
+
key.to_s[@tag, 1] if @tag
|
684
|
+
end
|
685
|
+
|
686
|
+
def ensure_same_node(command, *keys)
|
687
|
+
tags = keys.map { |key| key_tag(key) }
|
688
|
+
|
689
|
+
raise CannotDistribute, command if !tags.all? || tags.uniq.size != 1
|
690
|
+
|
691
|
+
yield(node_for(keys.first))
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|