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,20 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
worker_processes 3
|
4
|
+
|
5
|
+
# If you set the connection to Redis *before* forking,
|
6
|
+
# you will cause forks to share a file descriptor.
|
7
|
+
#
|
8
|
+
# This causes a concurrency problem by which one fork
|
9
|
+
# can read or write to the socket while others are
|
10
|
+
# performing other operations.
|
11
|
+
#
|
12
|
+
# Most likely you'll be getting ProtocolError exceptions
|
13
|
+
# mentioning a wrong initial byte in the reply.
|
14
|
+
#
|
15
|
+
# Thus we need to connect to Redis after forking the
|
16
|
+
# worker processes.
|
17
|
+
|
18
|
+
after_fork do |server, worker|
|
19
|
+
$redis = Redis.connect
|
20
|
+
end
|
data/lib/redis.rb
ADDED
@@ -0,0 +1,1166 @@
|
|
1
|
+
require "monitor"
|
2
|
+
require "redis/retry"
|
3
|
+
|
4
|
+
class Redis
|
5
|
+
class ProtocolError < RuntimeError
|
6
|
+
def initialize(reply_type)
|
7
|
+
super(<<-EOS.gsub(/(?:^|\n)\s*/, " "))
|
8
|
+
Got '#{reply_type}' as initial reply byte.
|
9
|
+
If you're running in a multi-threaded environment, make sure you
|
10
|
+
pass the :thread_safe option when initializing the connection.
|
11
|
+
If you're in a forking environment, such as Unicorn, you need to
|
12
|
+
connect to Redis after forking.
|
13
|
+
EOS
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.deprecate(message, trace = caller[0])
|
18
|
+
$stderr.puts "\n#{message} (in #{trace})"
|
19
|
+
end
|
20
|
+
|
21
|
+
attr :client
|
22
|
+
|
23
|
+
def self.connect(options = {})
|
24
|
+
options = options.dup
|
25
|
+
|
26
|
+
require "uri"
|
27
|
+
|
28
|
+
url = URI(options.delete(:url) || ENV["REDIS_URL"] || "redis://127.0.0.1:6379/0")
|
29
|
+
|
30
|
+
options[:host] ||= url.host
|
31
|
+
options[:port] ||= url.port
|
32
|
+
options[:password] ||= url.password
|
33
|
+
options[:db] ||= url.path[1..-1].to_i
|
34
|
+
|
35
|
+
new(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.current
|
39
|
+
Thread.current[:redis] ||= Redis.connect
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.current=(redis)
|
43
|
+
Thread.current[:redis] = redis
|
44
|
+
end
|
45
|
+
|
46
|
+
include MonitorMixin
|
47
|
+
|
48
|
+
def initialize(options = {})
|
49
|
+
super()
|
50
|
+
@options = options
|
51
|
+
@client = Client.new(options)
|
52
|
+
end
|
53
|
+
|
54
|
+
def wrapped
|
55
|
+
with_retry do
|
56
|
+
with_thread_safety do
|
57
|
+
yield
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_retry(&block)
|
63
|
+
@with_retry ||= (@options.keys & Redis::Retry::DEFAULT_OPTS.keys).any?
|
64
|
+
|
65
|
+
if ! @with_retry
|
66
|
+
block.call
|
67
|
+
else
|
68
|
+
@retry ||= Redis::Retry.new(client, @options)
|
69
|
+
@retry.execute(&block)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_thread_safety(&block)
|
74
|
+
if @options[:thread_safe] != false
|
75
|
+
synchronize(&block)
|
76
|
+
else
|
77
|
+
block.call
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Run code without the client reconnecting
|
82
|
+
def without_reconnect(&block)
|
83
|
+
wrapped do
|
84
|
+
@client.without_reconnect(&block)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Authenticate to the server.
|
89
|
+
def auth(password)
|
90
|
+
wrapped do
|
91
|
+
@client.call [:auth, password]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Change the selected database for the current connection.
|
96
|
+
def select(db)
|
97
|
+
wrapped do
|
98
|
+
@client.db = db
|
99
|
+
@client.call [:select, db]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get information and statistics about the server.
|
104
|
+
def info(cmd = nil)
|
105
|
+
wrapped do
|
106
|
+
reply = @client.call [:info, cmd].compact
|
107
|
+
|
108
|
+
if reply.kind_of?(String)
|
109
|
+
reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)]
|
110
|
+
|
111
|
+
if cmd && cmd.to_s == "commandstats"
|
112
|
+
# Extract nested hashes for INFO COMMANDSTATS
|
113
|
+
reply = Hash[reply.map do |k, v|
|
114
|
+
[k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]]
|
115
|
+
end]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
reply
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def config(action, *args)
|
124
|
+
wrapped do
|
125
|
+
reply = @client.call [:config, action, *args]
|
126
|
+
|
127
|
+
if reply.kind_of?(Array) && action == :get
|
128
|
+
Hash[*reply]
|
129
|
+
else
|
130
|
+
reply
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Remove all keys from the current database.
|
136
|
+
def flushdb
|
137
|
+
wrapped do
|
138
|
+
@client.call [:flushdb]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Remove all keys from all databases.
|
143
|
+
def flushall
|
144
|
+
wrapped do
|
145
|
+
@client.call [:flushall]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Synchronously save the dataset to disk.
|
150
|
+
def save
|
151
|
+
wrapped do
|
152
|
+
@client.call [:save]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Asynchronously save the dataset to disk.
|
157
|
+
def bgsave
|
158
|
+
wrapped do
|
159
|
+
@client.call [:bgsave]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Asynchronously rewrite the append-only file.
|
164
|
+
def bgrewriteaof
|
165
|
+
wrapped do
|
166
|
+
@client.call [:bgrewriteaof]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Get the value of a key.
|
171
|
+
def get(key)
|
172
|
+
wrapped do
|
173
|
+
@client.call [:get, key]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the bit value at offset in the string value stored at key.
|
178
|
+
def getbit(key, offset)
|
179
|
+
wrapped do
|
180
|
+
@client.call [:getbit, key, offset]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Get a substring of the string stored at a key.
|
185
|
+
def getrange(key, start, stop)
|
186
|
+
wrapped do
|
187
|
+
@client.call [:getrange, key, start, stop]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Set the string value of a key and return its old value.
|
192
|
+
def getset(key, value)
|
193
|
+
wrapped do
|
194
|
+
@client.call [:getset, key, value]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Get the values of all the given keys.
|
199
|
+
def mget(*keys)
|
200
|
+
wrapped do
|
201
|
+
@client.call [:mget, *keys]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Append a value to a key.
|
206
|
+
def append(key, value)
|
207
|
+
wrapped do
|
208
|
+
@client.call [:append, key, value]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def substr(key, start, stop)
|
213
|
+
wrapped do
|
214
|
+
@client.call [:substr, key, start, stop]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Get the length of the value stored in a key.
|
219
|
+
def strlen(key)
|
220
|
+
wrapped do
|
221
|
+
@client.call [:strlen, key]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Get all the fields and values in a hash.
|
226
|
+
def hgetall(key)
|
227
|
+
wrapped do
|
228
|
+
reply = @client.call [:hgetall, key]
|
229
|
+
|
230
|
+
if reply.kind_of?(Array)
|
231
|
+
Hash[*reply]
|
232
|
+
else
|
233
|
+
reply
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Get the value of a hash field.
|
239
|
+
def hget(key, field)
|
240
|
+
wrapped do
|
241
|
+
@client.call [:hget, key, field]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Delete a hash field.
|
246
|
+
def hdel(key, field)
|
247
|
+
wrapped do
|
248
|
+
@client.call [:hdel, key, field]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Get all the fields in a hash.
|
253
|
+
def hkeys(key)
|
254
|
+
wrapped do
|
255
|
+
@client.call [:hkeys, key]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Find all keys matching the given pattern.
|
260
|
+
def keys(pattern = "*")
|
261
|
+
wrapped do
|
262
|
+
reply = @client.call [:keys, pattern]
|
263
|
+
|
264
|
+
if reply.kind_of?(String)
|
265
|
+
reply.split(" ")
|
266
|
+
else
|
267
|
+
reply
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Return a random key from the keyspace.
|
273
|
+
def randomkey
|
274
|
+
wrapped do
|
275
|
+
@client.call [:randomkey]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Echo the given string.
|
280
|
+
def echo(value)
|
281
|
+
wrapped do
|
282
|
+
@client.call [:echo, value]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Ping the server.
|
287
|
+
def ping
|
288
|
+
wrapped do
|
289
|
+
@client.call [:ping]
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Get the UNIX time stamp of the last successful save to disk.
|
294
|
+
def lastsave
|
295
|
+
wrapped do
|
296
|
+
@client.call [:lastsave]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Return the number of keys in the selected database.
|
301
|
+
def dbsize
|
302
|
+
wrapped do
|
303
|
+
@client.call [:dbsize]
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Determine if a key exists.
|
308
|
+
def exists(key)
|
309
|
+
wrapped do
|
310
|
+
_bool @client.call [:exists, key]
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Get the length of a list.
|
315
|
+
def llen(key)
|
316
|
+
wrapped do
|
317
|
+
@client.call [:llen, key]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Get a range of elements from a list.
|
322
|
+
def lrange(key, start, stop)
|
323
|
+
wrapped do
|
324
|
+
@client.call [:lrange, key, start, stop]
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# Trim a list to the specified range.
|
329
|
+
def ltrim(key, start, stop)
|
330
|
+
wrapped do
|
331
|
+
@client.call [:ltrim, key, start, stop]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Get an element from a list by its index.
|
336
|
+
def lindex(key, index)
|
337
|
+
wrapped do
|
338
|
+
@client.call [:lindex, key, index]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Insert an element before or after another element in a list.
|
343
|
+
def linsert(key, where, pivot, value)
|
344
|
+
wrapped do
|
345
|
+
@client.call [:linsert, key, where, pivot, value]
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Set the value of an element in a list by its index.
|
350
|
+
def lset(key, index, value)
|
351
|
+
wrapped do
|
352
|
+
@client.call [:lset, key, index, value]
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Remove elements from a list.
|
357
|
+
def lrem(key, count, value)
|
358
|
+
wrapped do
|
359
|
+
@client.call [:lrem, key, count, value]
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Append a value to a list.
|
364
|
+
def rpush(key, value)
|
365
|
+
wrapped do
|
366
|
+
@client.call [:rpush, key, value]
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Append a value to a list, only if the list exists.
|
371
|
+
def rpushx(key, value)
|
372
|
+
wrapped do
|
373
|
+
@client.call [:rpushx, key, value]
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Prepend a value to a list.
|
378
|
+
def lpush(key, value)
|
379
|
+
wrapped do
|
380
|
+
@client.call [:lpush, key, value]
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Prepend a value to a list, only if the list exists.
|
385
|
+
def lpushx(key, value)
|
386
|
+
wrapped do
|
387
|
+
@client.call [:lpushx, key, value]
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# Remove and get the last element in a list.
|
392
|
+
def rpop(key)
|
393
|
+
wrapped do
|
394
|
+
@client.call [:rpop, key]
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Remove and get the first element in a list, or block until one is available.
|
399
|
+
def blpop(*args)
|
400
|
+
wrapped do
|
401
|
+
@client.call_without_timeout(:blpop, *args)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# Remove and get the last element in a list, or block until one is available.
|
406
|
+
def brpop(*args)
|
407
|
+
wrapped do
|
408
|
+
@client.call_without_timeout(:brpop, *args)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
# Pop a value from a list, push it to another list and return it; or block
|
413
|
+
# until one is available.
|
414
|
+
def brpoplpush(source, destination, timeout)
|
415
|
+
wrapped do
|
416
|
+
@client.call_without_timeout(:brpoplpush, source, destination, timeout)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# Remove the last element in a list, append it to another list and return it.
|
421
|
+
def rpoplpush(source, destination)
|
422
|
+
wrapped do
|
423
|
+
@client.call [:rpoplpush, source, destination]
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
# Remove and get the first element in a list.
|
428
|
+
def lpop(key)
|
429
|
+
wrapped do
|
430
|
+
@client.call [:lpop, key]
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
# Get all the members in a set.
|
435
|
+
def smembers(key)
|
436
|
+
wrapped do
|
437
|
+
@client.call [:smembers, key]
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# Determine if a given value is a member of a set.
|
442
|
+
def sismember(key, member)
|
443
|
+
wrapped do
|
444
|
+
_bool @client.call [:sismember, key, member]
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# Add a member to a set.
|
449
|
+
def sadd(key, value)
|
450
|
+
wrapped do
|
451
|
+
_bool @client.call [:sadd, key, value]
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
# Remove a member from a set.
|
456
|
+
def srem(key, value)
|
457
|
+
wrapped do
|
458
|
+
_bool @client.call [:srem, key, value]
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# Move a member from one set to another.
|
463
|
+
def smove(source, destination, member)
|
464
|
+
wrapped do
|
465
|
+
_bool @client.call [:smove, source, destination, member]
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# Remove and return a random member from a set.
|
470
|
+
def spop(key)
|
471
|
+
wrapped do
|
472
|
+
@client.call [:spop, key]
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# Get the number of members in a set.
|
477
|
+
def scard(key)
|
478
|
+
wrapped do
|
479
|
+
@client.call [:scard, key]
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# Intersect multiple sets.
|
484
|
+
def sinter(*keys)
|
485
|
+
wrapped do
|
486
|
+
@client.call [:sinter, *keys]
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# Intersect multiple sets and store the resulting set in a key.
|
491
|
+
def sinterstore(destination, *keys)
|
492
|
+
wrapped do
|
493
|
+
@client.call [:sinterstore, destination, *keys]
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
# Add multiple sets.
|
498
|
+
def sunion(*keys)
|
499
|
+
wrapped do
|
500
|
+
@client.call [:sunion, *keys]
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# Add multiple sets and store the resulting set in a key.
|
505
|
+
def sunionstore(destination, *keys)
|
506
|
+
wrapped do
|
507
|
+
@client.call [:sunionstore, destination, *keys]
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# Subtract multiple sets.
|
512
|
+
def sdiff(*keys)
|
513
|
+
wrapped do
|
514
|
+
@client.call [:sdiff, *keys]
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
# Subtract multiple sets and store the resulting set in a key.
|
519
|
+
def sdiffstore(destination, *keys)
|
520
|
+
wrapped do
|
521
|
+
@client.call [:sdiffstore, destination, *keys]
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
# Get a random member from a set.
|
526
|
+
def srandmember(key)
|
527
|
+
wrapped do
|
528
|
+
@client.call [:srandmember, key]
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
# Add a member to a sorted set, or update its score if it already exists.
|
533
|
+
def zadd(key, score, member)
|
534
|
+
wrapped do
|
535
|
+
_bool @client.call [:zadd, key, score, member]
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
# Determine the index of a member in a sorted set.
|
540
|
+
def zrank(key, member)
|
541
|
+
wrapped do
|
542
|
+
@client.call [:zrank, key, member]
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
# Determine the index of a member in a sorted set, with scores ordered from
|
547
|
+
# high to low.
|
548
|
+
def zrevrank(key, member)
|
549
|
+
wrapped do
|
550
|
+
@client.call [:zrevrank, key, member]
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
# Increment the score of a member in a sorted set.
|
555
|
+
def zincrby(key, increment, member)
|
556
|
+
wrapped do
|
557
|
+
@client.call [:zincrby, key, increment, member]
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
# Get the number of members in a sorted set.
|
562
|
+
def zcard(key)
|
563
|
+
wrapped do
|
564
|
+
@client.call [:zcard, key]
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
# Return a range of members in a sorted set, by index.
|
569
|
+
def zrange(key, start, stop, options = {})
|
570
|
+
command = CommandOptions.new(options) do |c|
|
571
|
+
c.bool :withscores
|
572
|
+
c.bool :with_scores
|
573
|
+
end
|
574
|
+
|
575
|
+
wrapped do
|
576
|
+
@client.call [:zrange, key, start, stop, *command.to_a]
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
# Return a range of members in a sorted set, by score.
|
581
|
+
def zrangebyscore(key, min, max, options = {})
|
582
|
+
command = CommandOptions.new(options) do |c|
|
583
|
+
c.splat :limit
|
584
|
+
c.bool :withscores
|
585
|
+
c.bool :with_scores
|
586
|
+
end
|
587
|
+
|
588
|
+
wrapped do
|
589
|
+
@client.call [:zrangebyscore, key, min, max, *command.to_a]
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
# Count the members in a sorted set with scores within the given values.
|
594
|
+
def zcount(key, start, stop)
|
595
|
+
wrapped do
|
596
|
+
@client.call [:zcount, key, start, stop]
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
# Return a range of members in a sorted set, by index, with scores ordered
|
601
|
+
# from high to low.
|
602
|
+
def zrevrange(key, start, stop, options = {})
|
603
|
+
command = CommandOptions.new(options) do |c|
|
604
|
+
c.bool :withscores
|
605
|
+
c.bool :with_scores
|
606
|
+
end
|
607
|
+
|
608
|
+
wrapped do
|
609
|
+
@client.call [:zrevrange, key, start, stop, *command.to_a]
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
# Return a range of members in a sorted set, by score, with scores ordered
|
614
|
+
# from high to low.
|
615
|
+
def zrevrangebyscore(key, max, min, options = {})
|
616
|
+
command = CommandOptions.new(options) do |c|
|
617
|
+
c.splat :limit
|
618
|
+
c.bool :withscores
|
619
|
+
c.bool :with_scores
|
620
|
+
end
|
621
|
+
|
622
|
+
wrapped do
|
623
|
+
@client.call [:zrevrangebyscore, key, max, min, *command.to_a]
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
# Remove all members in a sorted set within the given scores.
|
628
|
+
def zremrangebyscore(key, min, max)
|
629
|
+
wrapped do
|
630
|
+
@client.call [:zremrangebyscore, key, min, max]
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
# Remove all members in a sorted set within the given indexes.
|
635
|
+
def zremrangebyrank(key, start, stop)
|
636
|
+
wrapped do
|
637
|
+
@client.call [:zremrangebyrank, key, start, stop]
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
# Get the score associated with the given member in a sorted set.
|
642
|
+
def zscore(key, member)
|
643
|
+
wrapped do
|
644
|
+
@client.call [:zscore, key, member]
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
# Remove a member from a sorted set.
|
649
|
+
def zrem(key, member)
|
650
|
+
wrapped do
|
651
|
+
_bool @client.call [:zrem, key, member]
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
656
|
+
# key.
|
657
|
+
def zinterstore(destination, keys, options = {})
|
658
|
+
command = CommandOptions.new(options) do |c|
|
659
|
+
c.splat :weights
|
660
|
+
c.value :aggregate
|
661
|
+
end
|
662
|
+
|
663
|
+
wrapped do
|
664
|
+
@client.call [:zinterstore, destination, keys.size, *(keys + command.to_a)]
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
669
|
+
def zunionstore(destination, keys, options = {})
|
670
|
+
command = CommandOptions.new(options) do |c|
|
671
|
+
c.splat :weights
|
672
|
+
c.value :aggregate
|
673
|
+
end
|
674
|
+
|
675
|
+
wrapped do
|
676
|
+
@client.call [:zunionstore, destination, keys.size, *(keys + command.to_a)]
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
# Move a key to another database.
|
681
|
+
def move(key, db)
|
682
|
+
wrapped do
|
683
|
+
_bool @client.call [:move, key, db]
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
# Set the value of a key, only if the key does not exist.
|
688
|
+
def setnx(key, value)
|
689
|
+
wrapped do
|
690
|
+
_bool @client.call [:setnx, key, value]
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
# Delete a key.
|
695
|
+
def del(*keys)
|
696
|
+
wrapped do
|
697
|
+
@client.call [:del, *keys]
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
# Rename a key.
|
702
|
+
def rename(old_name, new_name)
|
703
|
+
wrapped do
|
704
|
+
@client.call [:rename, old_name, new_name]
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
# Rename a key, only if the new key does not exist.
|
709
|
+
def renamenx(old_name, new_name)
|
710
|
+
wrapped do
|
711
|
+
_bool @client.call [:renamenx, old_name, new_name]
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
# Set a key's time to live in seconds.
|
716
|
+
def expire(key, seconds)
|
717
|
+
wrapped do
|
718
|
+
_bool @client.call [:expire, key, seconds]
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
# Remove the expiration from a key.
|
723
|
+
def persist(key)
|
724
|
+
wrapped do
|
725
|
+
_bool @client.call [:persist, key]
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
# Get the time to live for a key.
|
730
|
+
def ttl(key)
|
731
|
+
wrapped do
|
732
|
+
@client.call [:ttl, key]
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
# Set the expiration for a key as a UNIX timestamp.
|
737
|
+
def expireat(key, unix_time)
|
738
|
+
wrapped do
|
739
|
+
_bool @client.call [:expireat, key, unix_time]
|
740
|
+
end
|
741
|
+
end
|
742
|
+
|
743
|
+
# Set the string value of a hash field.
|
744
|
+
def hset(key, field, value)
|
745
|
+
wrapped do
|
746
|
+
_bool @client.call [:hset, key, field, value]
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
# Set the value of a hash field, only if the field does not exist.
|
751
|
+
def hsetnx(key, field, value)
|
752
|
+
wrapped do
|
753
|
+
_bool @client.call [:hsetnx, key, field, value]
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
# Set multiple hash fields to multiple values.
|
758
|
+
def hmset(key, *attrs)
|
759
|
+
wrapped do
|
760
|
+
@client.call [:hmset, key, *attrs]
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
def mapped_hmset(key, hash)
|
765
|
+
hmset(key, *hash.to_a.flatten)
|
766
|
+
end
|
767
|
+
|
768
|
+
# Get the values of all the given hash fields.
|
769
|
+
def hmget(key, *fields)
|
770
|
+
wrapped do
|
771
|
+
@client.call [:hmget, key, *fields]
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
def mapped_hmget(key, *fields)
|
776
|
+
reply = hmget(key, *fields)
|
777
|
+
|
778
|
+
if reply.kind_of?(Array)
|
779
|
+
Hash[*fields.zip(reply).flatten]
|
780
|
+
else
|
781
|
+
reply
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
# Get the number of fields in a hash.
|
786
|
+
def hlen(key)
|
787
|
+
wrapped do
|
788
|
+
@client.call [:hlen, key]
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
792
|
+
# Get all the values in a hash.
|
793
|
+
def hvals(key)
|
794
|
+
wrapped do
|
795
|
+
@client.call [:hvals, key]
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
# Increment the integer value of a hash field by the given number.
|
800
|
+
def hincrby(key, field, increment)
|
801
|
+
wrapped do
|
802
|
+
@client.call [:hincrby, key, field, increment]
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
# Discard all commands issued after MULTI.
|
807
|
+
def discard
|
808
|
+
wrapped do
|
809
|
+
@client.call [:discard]
|
810
|
+
end
|
811
|
+
end
|
812
|
+
|
813
|
+
# Determine if a hash field exists.
|
814
|
+
def hexists(key, field)
|
815
|
+
wrapped do
|
816
|
+
_bool @client.call [:hexists, key, field]
|
817
|
+
end
|
818
|
+
end
|
819
|
+
|
820
|
+
# Listen for all requests received by the server in real time.
|
821
|
+
def monitor(&block)
|
822
|
+
wrapped do
|
823
|
+
@client.call_loop([:monitor], &block)
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
def debug(*args)
|
828
|
+
wrapped do
|
829
|
+
@client.call [:debug, *args]
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
def object(*args)
|
834
|
+
wrapped do
|
835
|
+
@client.call [:object, *args]
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
# Internal command used for replication.
|
840
|
+
def sync
|
841
|
+
wrapped do
|
842
|
+
@client.call [:sync]
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
def [](key)
|
847
|
+
get(key)
|
848
|
+
end
|
849
|
+
|
850
|
+
def []=(key,value)
|
851
|
+
set(key, value)
|
852
|
+
end
|
853
|
+
|
854
|
+
# Set the string value of a key.
|
855
|
+
def set(key, value)
|
856
|
+
wrapped do
|
857
|
+
@client.call [:set, key, value]
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
# Sets or clears the bit at offset in the string value stored at key.
|
862
|
+
def setbit(key, offset, value)
|
863
|
+
wrapped do
|
864
|
+
@client.call [:setbit, key, offset, value]
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
# Set the value and expiration of a key.
|
869
|
+
def setex(key, ttl, value)
|
870
|
+
wrapped do
|
871
|
+
@client.call [:setex, key, ttl, value]
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
# Overwrite part of a string at key starting at the specified offset.
|
876
|
+
def setrange(key, offset, value)
|
877
|
+
wrapped do
|
878
|
+
@client.call [:setrange, key, offset, value]
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
882
|
+
# Set multiple keys to multiple values.
|
883
|
+
def mset(*args)
|
884
|
+
wrapped do
|
885
|
+
@client.call [:mset, *args]
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
889
|
+
def mapped_mset(hash)
|
890
|
+
mset(*hash.to_a.flatten)
|
891
|
+
end
|
892
|
+
|
893
|
+
# Set multiple keys to multiple values, only if none of the keys exist.
|
894
|
+
def msetnx(*args)
|
895
|
+
wrapped do
|
896
|
+
@client.call [:msetnx, *args]
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
def mapped_msetnx(hash)
|
901
|
+
msetnx(*hash.to_a.flatten)
|
902
|
+
end
|
903
|
+
|
904
|
+
def mapped_mget(*keys)
|
905
|
+
reply = mget(*keys)
|
906
|
+
|
907
|
+
if reply.kind_of?(Array)
|
908
|
+
Hash[*keys.zip(reply).flatten]
|
909
|
+
else
|
910
|
+
reply
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
914
|
+
# Sort the elements in a list, set or sorted set.
|
915
|
+
def sort(key, options = {})
|
916
|
+
command = CommandOptions.new(options) do |c|
|
917
|
+
c.value :by
|
918
|
+
c.splat :limit
|
919
|
+
c.multi :get
|
920
|
+
c.words :order
|
921
|
+
c.value :store
|
922
|
+
end
|
923
|
+
|
924
|
+
wrapped do
|
925
|
+
@client.call [:sort, key, *command.to_a]
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
# Increment the integer value of a key by one.
|
930
|
+
def incr(key)
|
931
|
+
wrapped do
|
932
|
+
@client.call [:incr, key]
|
933
|
+
end
|
934
|
+
end
|
935
|
+
|
936
|
+
# Increment the integer value of a key by the given number.
|
937
|
+
def incrby(key, increment)
|
938
|
+
wrapped do
|
939
|
+
@client.call [:incrby, key, increment]
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
|
+
# Decrement the integer value of a key by one.
|
944
|
+
def decr(key)
|
945
|
+
wrapped do
|
946
|
+
@client.call [:decr, key]
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
# Decrement the integer value of a key by the given number.
|
951
|
+
def decrby(key, decrement)
|
952
|
+
wrapped do
|
953
|
+
@client.call [:decrby, key, decrement]
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
# Determine the type stored at key.
|
958
|
+
def type(key)
|
959
|
+
wrapped do
|
960
|
+
@client.call [:type, key]
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
# Close the connection.
|
965
|
+
def quit
|
966
|
+
wrapped do
|
967
|
+
begin
|
968
|
+
@client.call [:quit]
|
969
|
+
rescue Errno::ECONNRESET
|
970
|
+
ensure
|
971
|
+
@client.disconnect
|
972
|
+
end
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
# Synchronously save the dataset to disk and then shut down the server.
|
977
|
+
def shutdown
|
978
|
+
wrapped do
|
979
|
+
@client.call_without_reply [:shutdown]
|
980
|
+
end
|
981
|
+
end
|
982
|
+
|
983
|
+
# Make the server a slave of another instance, or promote it as master.
|
984
|
+
def slaveof(host, port)
|
985
|
+
wrapped do
|
986
|
+
@client.call [:slaveof, host, port]
|
987
|
+
end
|
988
|
+
end
|
989
|
+
|
990
|
+
def pipelined(options = {})
|
991
|
+
wrapped do
|
992
|
+
begin
|
993
|
+
original, @client = @client, Pipeline.new
|
994
|
+
yield
|
995
|
+
original.call_pipelined(@client.commands, options) unless @client.commands.empty?
|
996
|
+
ensure
|
997
|
+
@client = original
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
1003
|
+
def watch(*keys)
|
1004
|
+
wrapped do
|
1005
|
+
@client.call [:watch, *keys]
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# Forget about all watched keys.
|
1010
|
+
def unwatch
|
1011
|
+
wrapped do
|
1012
|
+
@client.call [:unwatch]
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# Execute all commands issued after MULTI.
|
1017
|
+
def exec
|
1018
|
+
wrapped do
|
1019
|
+
@client.call [:exec]
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
# Mark the start of a transaction block.
|
1024
|
+
def multi
|
1025
|
+
wrapped do
|
1026
|
+
if !block_given?
|
1027
|
+
@client.call :multi
|
1028
|
+
else
|
1029
|
+
result = pipelined(:raise => false) do
|
1030
|
+
multi
|
1031
|
+
yield(self)
|
1032
|
+
exec
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
result.last
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
# Post a message to a channel.
|
1041
|
+
def publish(channel, message)
|
1042
|
+
wrapped do
|
1043
|
+
@client.call [:publish, channel, message]
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
def subscribed?
|
1048
|
+
wrapped do
|
1049
|
+
@client.kind_of? SubscribedClient
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
# Stop listening for messages posted to the given channels.
|
1054
|
+
def unsubscribe(*channels)
|
1055
|
+
wrapped do
|
1056
|
+
raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
|
1057
|
+
@client.unsubscribe(*channels)
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
# Stop listening for messages posted to channels matching the given patterns.
|
1062
|
+
def punsubscribe(*channels)
|
1063
|
+
wrapped do
|
1064
|
+
raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
|
1065
|
+
@client.punsubscribe(*channels)
|
1066
|
+
end
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
# Listen for messages published to the given channels.
|
1070
|
+
def subscribe(*channels, &block)
|
1071
|
+
wrapped do
|
1072
|
+
subscription(:subscribe, channels, block)
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
# Listen for messages published to channels matching the given patterns.
|
1077
|
+
def psubscribe(*channels, &block)
|
1078
|
+
wrapped do
|
1079
|
+
subscription(:psubscribe, channels, block)
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
def id
|
1084
|
+
wrapped do
|
1085
|
+
@client.id
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
def inspect
|
1090
|
+
wrapped do
|
1091
|
+
"#<Redis client v#{Redis::VERSION} connected to #{id} (Redis v#{info["redis_version"]})>"
|
1092
|
+
end
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
def method_missing(command, *args)
|
1096
|
+
wrapped do
|
1097
|
+
@client.call [command, *args]
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
class CommandOptions
|
1102
|
+
def initialize(options)
|
1103
|
+
@result = []
|
1104
|
+
@options = options
|
1105
|
+
yield(self)
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
def bool(name)
|
1109
|
+
insert(name) { |argument, value| [argument] }
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def value(name)
|
1113
|
+
insert(name) { |argument, value| [argument, value] }
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
def splat(name)
|
1117
|
+
insert(name) { |argument, value| [argument, *value] }
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def multi(name)
|
1121
|
+
insert(name) { |argument, value| [argument].product(Array(value)).flatten }
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
def words(name)
|
1125
|
+
insert(name) { |argument, value| value.split(" ") }
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
def to_a
|
1129
|
+
@result
|
1130
|
+
end
|
1131
|
+
|
1132
|
+
def insert(name)
|
1133
|
+
@result += yield(name.to_s.upcase.gsub("_", ""), @options[name]) if @options[name]
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
private
|
1138
|
+
|
1139
|
+
# Commands returning 1 for true and 0 for false may be executed in a pipeline
|
1140
|
+
# where the method call will return nil. Propagate the nil instead of falsely
|
1141
|
+
# returning false.
|
1142
|
+
def _bool(value)
|
1143
|
+
value == 1 if value
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
def subscription(method, channels, block)
|
1147
|
+
return @client.call [method, *channels] if subscribed?
|
1148
|
+
|
1149
|
+
begin
|
1150
|
+
original, @client = @client, SubscribedClient.new(@client)
|
1151
|
+
@client.send(method, *channels, &block)
|
1152
|
+
ensure
|
1153
|
+
@client = original
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
require "redis/version"
|
1160
|
+
require "redis/connection"
|
1161
|
+
require "redis/client"
|
1162
|
+
require "redis/pipeline"
|
1163
|
+
require "redis/subscribe"
|
1164
|
+
require "redis/compat"
|
1165
|
+
require "redis/distributed"
|
1166
|
+
require "redis/hash_ring"
|