redis 4.0.3 → 4.5.0
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +110 -0
- data/README.md +126 -17
- data/lib/redis/client.rb +130 -82
- data/lib/redis/cluster/command_loader.rb +8 -7
- data/lib/redis/cluster/node.rb +5 -1
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/node_loader.rb +2 -0
- data/lib/redis/cluster/option.rb +31 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +6 -4
- data/lib/redis/cluster.rb +23 -17
- data/lib/redis/connection/command_helper.rb +5 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +139 -106
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/distributed.rb +171 -70
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +46 -8
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +1239 -426
- metadata +16 -262
- data/.gitignore +0 -19
- data/.travis/Gemfile +0 -18
- data/.travis.yml +0 -61
- data/.yardopts +0 -3
- data/Gemfile +0 -8
- data/benchmarking/logging.rb +0 -71
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/bin/build +0 -71
- data/bors.toml +0 -14
- data/examples/basic.rb +0 -15
- data/examples/consistency.rb +0 -114
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -37
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sentinel.rb +0 -41
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/makefile +0 -74
- data/redis.gemspec +0 -43
- data/test/bitpos_test.rb +0 -63
- data/test/blocking_commands_test.rb +0 -40
- data/test/client_test.rb +0 -76
- data/test/cluster_abnormal_state_test.rb +0 -38
- data/test/cluster_blocking_commands_test.rb +0 -15
- data/test/cluster_client_internals_test.rb +0 -77
- data/test/cluster_client_key_hash_tags_test.rb +0 -88
- data/test/cluster_client_options_test.rb +0 -147
- data/test/cluster_client_pipelining_test.rb +0 -59
- data/test/cluster_client_replicas_test.rb +0 -36
- data/test/cluster_client_slots_test.rb +0 -94
- data/test/cluster_client_transactions_test.rb +0 -71
- data/test/cluster_commands_on_cluster_test.rb +0 -165
- data/test/cluster_commands_on_connection_test.rb +0 -40
- data/test/cluster_commands_on_geo_test.rb +0 -74
- data/test/cluster_commands_on_hashes_test.rb +0 -11
- data/test/cluster_commands_on_hyper_log_log_test.rb +0 -17
- data/test/cluster_commands_on_keys_test.rb +0 -134
- data/test/cluster_commands_on_lists_test.rb +0 -15
- data/test/cluster_commands_on_pub_sub_test.rb +0 -101
- data/test/cluster_commands_on_scripting_test.rb +0 -56
- data/test/cluster_commands_on_server_test.rb +0 -221
- data/test/cluster_commands_on_sets_test.rb +0 -39
- data/test/cluster_commands_on_sorted_sets_test.rb +0 -35
- data/test/cluster_commands_on_streams_test.rb +0 -196
- data/test/cluster_commands_on_strings_test.rb +0 -15
- data/test/cluster_commands_on_transactions_test.rb +0 -41
- data/test/cluster_commands_on_value_types_test.rb +0 -14
- data/test/command_map_test.rb +0 -28
- data/test/commands_on_geo_test.rb +0 -116
- data/test/commands_on_hashes_test.rb +0 -7
- data/test/commands_on_hyper_log_log_test.rb +0 -7
- data/test/commands_on_lists_test.rb +0 -7
- data/test/commands_on_sets_test.rb +0 -7
- data/test/commands_on_sorted_sets_test.rb +0 -7
- data/test/commands_on_strings_test.rb +0 -7
- data/test/commands_on_value_types_test.rb +0 -207
- data/test/connection_handling_test.rb +0 -275
- data/test/connection_test.rb +0 -57
- data/test/db/.gitkeep +0 -0
- data/test/distributed_blocking_commands_test.rb +0 -52
- data/test/distributed_commands_on_hashes_test.rb +0 -21
- data/test/distributed_commands_on_hyper_log_log_test.rb +0 -26
- data/test/distributed_commands_on_lists_test.rb +0 -19
- data/test/distributed_commands_on_sets_test.rb +0 -105
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -59
- data/test/distributed_commands_on_strings_test.rb +0 -79
- data/test/distributed_commands_on_value_types_test.rb +0 -129
- data/test/distributed_commands_requiring_clustering_test.rb +0 -162
- data/test/distributed_connection_handling_test.rb +0 -21
- data/test/distributed_internals_test.rb +0 -68
- data/test/distributed_key_tags_test.rb +0 -50
- data/test/distributed_persistence_control_commands_test.rb +0 -24
- data/test/distributed_publish_subscribe_test.rb +0 -90
- data/test/distributed_remote_server_control_commands_test.rb +0 -64
- data/test/distributed_scripting_test.rb +0 -100
- data/test/distributed_sorting_test.rb +0 -18
- data/test/distributed_test.rb +0 -56
- data/test/distributed_transactions_test.rb +0 -30
- data/test/encoding_test.rb +0 -14
- data/test/error_replies_test.rb +0 -57
- data/test/fork_safety_test.rb +0 -60
- data/test/helper.rb +0 -345
- data/test/helper_test.rb +0 -22
- data/test/internals_test.rb +0 -408
- data/test/lint/blocking_commands.rb +0 -174
- data/test/lint/hashes.rb +0 -203
- data/test/lint/hyper_log_log.rb +0 -74
- data/test/lint/lists.rb +0 -159
- data/test/lint/sets.rb +0 -282
- data/test/lint/sorted_sets.rb +0 -497
- data/test/lint/strings.rb +0 -348
- data/test/lint/value_types.rb +0 -130
- data/test/persistence_control_commands_test.rb +0 -24
- data/test/pipelining_commands_test.rb +0 -246
- data/test/publish_subscribe_test.rb +0 -280
- data/test/remote_server_control_commands_test.rb +0 -175
- data/test/scanning_test.rb +0 -407
- data/test/scripting_test.rb +0 -76
- data/test/sentinel_command_test.rb +0 -78
- data/test/sentinel_test.rb +0 -253
- data/test/sorting_test.rb +0 -57
- data/test/ssl_test.rb +0 -69
- data/test/support/cluster/orchestrator.rb +0 -199
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -130
- data/test/support/ssl/gen_certs.sh +0 -31
- data/test/support/ssl/trusted-ca.crt +0 -25
- data/test/support/ssl/trusted-ca.key +0 -27
- data/test/support/ssl/trusted-cert.crt +0 -81
- data/test/support/ssl/trusted-cert.key +0 -28
- data/test/support/ssl/untrusted-ca.crt +0 -26
- data/test/support/ssl/untrusted-ca.key +0 -27
- data/test/support/ssl/untrusted-cert.crt +0 -82
- data/test/support/ssl/untrusted-cert.key +0 -28
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -85
- data/test/test.conf.erb +0 -9
- data/test/thread_safety_test.rb +0 -60
- data/test/transactions_test.rb +0 -272
- data/test/unknown_commands_test.rb +0 -12
- data/test/url_param_test.rb +0 -136
data/lib/redis/distributed.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "hash_ring"
|
2
4
|
|
3
5
|
class Redis
|
4
6
|
class Distributed
|
5
|
-
|
6
7
|
class CannotDistribute < RuntimeError
|
7
8
|
def initialize(command)
|
8
9
|
@command = command
|
9
10
|
end
|
10
11
|
|
11
12
|
def message
|
12
|
-
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need
|
13
|
+
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need " \
|
14
|
+
"to be on the same server or because we cannot guarantee that the operation will be atomic."
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -22,10 +24,14 @@ class Redis
|
|
22
24
|
@default_options = options.dup
|
23
25
|
node_configs.each { |node_config| add_node(node_config) }
|
24
26
|
@subscribed_node = nil
|
27
|
+
@watch_key = nil
|
25
28
|
end
|
26
29
|
|
27
30
|
def node_for(key)
|
28
|
-
|
31
|
+
key = key_tag(key.to_s) || key.to_s
|
32
|
+
raise CannotDistribute, :watch if @watch_key && @watch_key != key
|
33
|
+
|
34
|
+
@ring.get_node(key)
|
29
35
|
end
|
30
36
|
|
31
37
|
def nodes
|
@@ -33,9 +39,9 @@ class Redis
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def add_node(options)
|
36
|
-
options = { :
|
42
|
+
options = { url: options } if options.is_a?(String)
|
37
43
|
options = @default_options.merge(options)
|
38
|
-
@ring.add_node Redis.new(
|
44
|
+
@ring.add_node Redis.new(options)
|
39
45
|
end
|
40
46
|
|
41
47
|
# Change the selected database for the current connection.
|
@@ -144,12 +150,12 @@ class Redis
|
|
144
150
|
end
|
145
151
|
|
146
152
|
# Create a key using the serialized value, previously obtained using DUMP.
|
147
|
-
def restore(key, ttl, serialized_value, options
|
148
|
-
node_for(key).restore(key, ttl, serialized_value, options)
|
153
|
+
def restore(key, ttl, serialized_value, **options)
|
154
|
+
node_for(key).restore(key, ttl, serialized_value, **options)
|
149
155
|
end
|
150
156
|
|
151
157
|
# Transfer a key from the connected instance to another instance.
|
152
|
-
def migrate(
|
158
|
+
def migrate(_key, _options)
|
153
159
|
raise CannotDistribute, :migrate
|
154
160
|
end
|
155
161
|
|
@@ -170,8 +176,33 @@ class Redis
|
|
170
176
|
end
|
171
177
|
|
172
178
|
# Determine if a key exists.
|
173
|
-
def exists(
|
174
|
-
|
179
|
+
def exists(*args)
|
180
|
+
if !Redis.exists_returns_integer && args.size == 1
|
181
|
+
message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
|
182
|
+
"use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
|
183
|
+
"(#{::Kernel.caller(1, 1).first})\n"
|
184
|
+
|
185
|
+
if defined?(::Warning)
|
186
|
+
::Warning.warn(message)
|
187
|
+
else
|
188
|
+
warn(message)
|
189
|
+
end
|
190
|
+
exists?(*args)
|
191
|
+
else
|
192
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
193
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
194
|
+
sum + node._exists(*keys)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Determine if any of the keys exists.
|
200
|
+
def exists?(*args)
|
201
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
202
|
+
keys_per_node.each do |node, keys|
|
203
|
+
return true if node.exists?(*keys)
|
204
|
+
end
|
205
|
+
false
|
175
206
|
end
|
176
207
|
|
177
208
|
# Find all keys matching the given pattern.
|
@@ -204,11 +235,11 @@ class Redis
|
|
204
235
|
end
|
205
236
|
|
206
237
|
# Sort the elements in a list, set or sorted set.
|
207
|
-
def sort(key, options
|
238
|
+
def sort(key, **options)
|
208
239
|
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
209
240
|
|
210
241
|
ensure_same_node(:sort, keys) do |node|
|
211
|
-
node.sort(key, options)
|
242
|
+
node.sort(key, **options)
|
212
243
|
end
|
213
244
|
end
|
214
245
|
|
@@ -243,8 +274,8 @@ class Redis
|
|
243
274
|
end
|
244
275
|
|
245
276
|
# Set the string value of a key.
|
246
|
-
def set(key, value, options
|
247
|
-
node_for(key).set(key, value, options)
|
277
|
+
def set(key, value, **options)
|
278
|
+
node_for(key).set(key, value, **options)
|
248
279
|
end
|
249
280
|
|
250
281
|
# Set the time to live in seconds of a key.
|
@@ -263,20 +294,20 @@ class Redis
|
|
263
294
|
end
|
264
295
|
|
265
296
|
# Set multiple keys to multiple values.
|
266
|
-
def mset(*
|
297
|
+
def mset(*_args)
|
267
298
|
raise CannotDistribute, :mset
|
268
299
|
end
|
269
300
|
|
270
|
-
def mapped_mset(
|
301
|
+
def mapped_mset(_hash)
|
271
302
|
raise CannotDistribute, :mapped_mset
|
272
303
|
end
|
273
304
|
|
274
305
|
# Set multiple keys to multiple values, only if none of the keys exist.
|
275
|
-
def msetnx(*
|
306
|
+
def msetnx(*_args)
|
276
307
|
raise CannotDistribute, :msetnx
|
277
308
|
end
|
278
309
|
|
279
|
-
def mapped_msetnx(
|
310
|
+
def mapped_msetnx(_hash)
|
280
311
|
raise CannotDistribute, :mapped_msetnx
|
281
312
|
end
|
282
313
|
|
@@ -285,6 +316,16 @@ class Redis
|
|
285
316
|
node_for(key).get(key)
|
286
317
|
end
|
287
318
|
|
319
|
+
# Get the value of a key and delete it.
|
320
|
+
def getdel(key)
|
321
|
+
node_for(key).getdel(key)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Get the value of a key and sets its time to live based on options.
|
325
|
+
def getex(key, **options)
|
326
|
+
node_for(key).getex(key, **options)
|
327
|
+
end
|
328
|
+
|
288
329
|
# Get the values of all the given keys as an Array.
|
289
330
|
def mget(*keys)
|
290
331
|
mapped_mget(*keys).values_at(*keys)
|
@@ -335,7 +376,7 @@ class Redis
|
|
335
376
|
end
|
336
377
|
|
337
378
|
# Return the position of the first bit set to 1 or 0 in a string.
|
338
|
-
def bitpos(key, bit, start=nil, stop=nil)
|
379
|
+
def bitpos(key, bit, start = nil, stop = nil)
|
339
380
|
node_for(key).bitpos(key, bit, start, stop)
|
340
381
|
end
|
341
382
|
|
@@ -353,7 +394,7 @@ class Redis
|
|
353
394
|
get(key)
|
354
395
|
end
|
355
396
|
|
356
|
-
def []=(key,value)
|
397
|
+
def []=(key, value)
|
357
398
|
set(key, value)
|
358
399
|
end
|
359
400
|
|
@@ -362,6 +403,21 @@ class Redis
|
|
362
403
|
node_for(key).llen(key)
|
363
404
|
end
|
364
405
|
|
406
|
+
# Remove the first/last element in a list, append/prepend it to another list and return it.
|
407
|
+
def lmove(source, destination, where_source, where_destination)
|
408
|
+
ensure_same_node(:lmove, [source, destination]) do |node|
|
409
|
+
node.lmove(source, destination, where_source, where_destination)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
# Remove the first/last element in a list and append/prepend it
|
414
|
+
# to another list and return it, or block until one is available.
|
415
|
+
def blmove(source, destination, where_source, where_destination, timeout: 0)
|
416
|
+
ensure_same_node(:lmove, [source, destination]) do |node|
|
417
|
+
node.blmove(source, destination, where_source, where_destination, timeout: timeout)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
365
421
|
# Prepend one or more values to a list.
|
366
422
|
def lpush(key, value)
|
367
423
|
node_for(key).lpush(key, value)
|
@@ -382,14 +438,14 @@ class Redis
|
|
382
438
|
node_for(key).rpushx(key, value)
|
383
439
|
end
|
384
440
|
|
385
|
-
# Remove and get the first
|
386
|
-
def lpop(key)
|
387
|
-
node_for(key).lpop(key)
|
441
|
+
# Remove and get the first elements in a list.
|
442
|
+
def lpop(key, count = nil)
|
443
|
+
node_for(key).lpop(key, count)
|
388
444
|
end
|
389
445
|
|
390
|
-
# Remove and get the last
|
391
|
-
def rpop(key)
|
392
|
-
node_for(key).rpop(key)
|
446
|
+
# Remove and get the last elements in a list.
|
447
|
+
def rpop(key, count = nil)
|
448
|
+
node_for(key).rpop(key, count)
|
393
449
|
end
|
394
450
|
|
395
451
|
# Remove the last element in a list, append it to another list and return
|
@@ -401,14 +457,12 @@ class Redis
|
|
401
457
|
end
|
402
458
|
|
403
459
|
def _bpop(cmd, args)
|
404
|
-
|
405
|
-
|
406
|
-
case args.last
|
407
|
-
when Hash
|
460
|
+
timeout = if args.last.is_a?(Hash)
|
408
461
|
options = args.pop
|
409
|
-
|
462
|
+
options[:timeout]
|
463
|
+
elsif args.last.respond_to?(:to_int)
|
410
464
|
# Issue deprecation notice in obnoxious mode...
|
411
|
-
|
465
|
+
args.pop.to_int
|
412
466
|
end
|
413
467
|
|
414
468
|
if args.size > 1
|
@@ -418,7 +472,11 @@ class Redis
|
|
418
472
|
keys = args.flatten
|
419
473
|
|
420
474
|
ensure_same_node(cmd, keys) do |node|
|
421
|
-
|
475
|
+
if timeout
|
476
|
+
node.__send__(cmd, keys, timeout: timeout)
|
477
|
+
else
|
478
|
+
node.__send__(cmd, keys)
|
479
|
+
end
|
422
480
|
end
|
423
481
|
end
|
424
482
|
|
@@ -436,15 +494,9 @@ class Redis
|
|
436
494
|
|
437
495
|
# Pop a value from a list, push it to another list and return it; or block
|
438
496
|
# until one is available.
|
439
|
-
def brpoplpush(source, destination,
|
440
|
-
case options
|
441
|
-
when Integer
|
442
|
-
# Issue deprecation notice in obnoxious mode...
|
443
|
-
options = { :timeout => options }
|
444
|
-
end
|
445
|
-
|
497
|
+
def brpoplpush(source, destination, deprecated_timeout = 0, **options)
|
446
498
|
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
447
|
-
node.brpoplpush(source, destination, options)
|
499
|
+
node.brpoplpush(source, destination, deprecated_timeout, **options)
|
448
500
|
end
|
449
501
|
end
|
450
502
|
|
@@ -515,19 +567,24 @@ class Redis
|
|
515
567
|
node_for(key).sismember(key, member)
|
516
568
|
end
|
517
569
|
|
570
|
+
# Determine if multiple values are members of a set.
|
571
|
+
def smismember(key, *members)
|
572
|
+
node_for(key).smismember(key, *members)
|
573
|
+
end
|
574
|
+
|
518
575
|
# Get all the members in a set.
|
519
576
|
def smembers(key)
|
520
577
|
node_for(key).smembers(key)
|
521
578
|
end
|
522
579
|
|
523
580
|
# Scan a set
|
524
|
-
def sscan(key, cursor, options
|
525
|
-
node_for(key).sscan(key, cursor, options)
|
581
|
+
def sscan(key, cursor, **options)
|
582
|
+
node_for(key).sscan(key, cursor, **options)
|
526
583
|
end
|
527
584
|
|
528
585
|
# Scan a set and return an enumerator
|
529
|
-
def sscan_each(key, options
|
530
|
-
node_for(key).sscan_each(key, options, &block)
|
586
|
+
def sscan_each(key, **options, &block)
|
587
|
+
node_for(key).sscan_each(key, **options, &block)
|
531
588
|
end
|
532
589
|
|
533
590
|
# Subtract multiple sets.
|
@@ -582,6 +639,7 @@ class Redis
|
|
582
639
|
def zadd(key, *args)
|
583
640
|
node_for(key).zadd(key, *args)
|
584
641
|
end
|
642
|
+
ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
|
585
643
|
|
586
644
|
# Increment the score of a member in a sorted set.
|
587
645
|
def zincrby(key, increment, member)
|
@@ -598,15 +656,25 @@ class Redis
|
|
598
656
|
node_for(key).zscore(key, member)
|
599
657
|
end
|
600
658
|
|
659
|
+
# Get one or more random members from a sorted set.
|
660
|
+
def zrandmember(key, count = nil, **options)
|
661
|
+
node_for(key).zrandmember(key, count, **options)
|
662
|
+
end
|
663
|
+
|
664
|
+
# Get the scores associated with the given members in a sorted set.
|
665
|
+
def zmscore(key, *members)
|
666
|
+
node_for(key).zmscore(key, *members)
|
667
|
+
end
|
668
|
+
|
601
669
|
# Return a range of members in a sorted set, by index.
|
602
|
-
def zrange(key, start, stop, options
|
603
|
-
node_for(key).zrange(key, start, stop, options)
|
670
|
+
def zrange(key, start, stop, **options)
|
671
|
+
node_for(key).zrange(key, start, stop, **options)
|
604
672
|
end
|
605
673
|
|
606
674
|
# Return a range of members in a sorted set, by index, with scores ordered
|
607
675
|
# from high to low.
|
608
|
-
def zrevrange(key, start, stop, options
|
609
|
-
node_for(key).zrevrange(key, start, stop, options)
|
676
|
+
def zrevrange(key, start, stop, **options)
|
677
|
+
node_for(key).zrevrange(key, start, stop, **options)
|
610
678
|
end
|
611
679
|
|
612
680
|
# Determine the index of a member in a sorted set.
|
@@ -626,14 +694,14 @@ class Redis
|
|
626
694
|
end
|
627
695
|
|
628
696
|
# Return a range of members in a sorted set, by score.
|
629
|
-
def zrangebyscore(key, min, max, options
|
630
|
-
node_for(key).zrangebyscore(key, min, max, options)
|
697
|
+
def zrangebyscore(key, min, max, **options)
|
698
|
+
node_for(key).zrangebyscore(key, min, max, **options)
|
631
699
|
end
|
632
700
|
|
633
701
|
# Return a range of members in a sorted set, by score, with scores ordered
|
634
702
|
# from high to low.
|
635
|
-
def zrevrangebyscore(key, max, min, options
|
636
|
-
node_for(key).zrevrangebyscore(key, max, min, options)
|
703
|
+
def zrevrangebyscore(key, max, min, **options)
|
704
|
+
node_for(key).zrevrangebyscore(key, max, min, **options)
|
637
705
|
end
|
638
706
|
|
639
707
|
# Remove all members in a sorted set within the given scores.
|
@@ -646,18 +714,25 @@ class Redis
|
|
646
714
|
node_for(key).zcount(key, min, max)
|
647
715
|
end
|
648
716
|
|
717
|
+
# Get the intersection of multiple sorted sets
|
718
|
+
def zinter(*keys, **options)
|
719
|
+
ensure_same_node(:zinter, keys) do |node|
|
720
|
+
node.zinter(*keys, **options)
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
649
724
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
650
725
|
# key.
|
651
|
-
def zinterstore(destination, keys, options
|
726
|
+
def zinterstore(destination, keys, **options)
|
652
727
|
ensure_same_node(:zinterstore, [destination] + keys) do |node|
|
653
|
-
node.zinterstore(destination, keys, options)
|
728
|
+
node.zinterstore(destination, keys, **options)
|
654
729
|
end
|
655
730
|
end
|
656
731
|
|
657
732
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
658
|
-
def zunionstore(destination, keys, options
|
733
|
+
def zunionstore(destination, keys, **options)
|
659
734
|
ensure_same_node(:zunionstore, [destination] + keys) do |node|
|
660
|
-
node.zunionstore(destination, keys, options)
|
735
|
+
node.zunionstore(destination, keys, **options)
|
661
736
|
end
|
662
737
|
end
|
663
738
|
|
@@ -666,9 +741,9 @@ class Redis
|
|
666
741
|
node_for(key).hlen(key)
|
667
742
|
end
|
668
743
|
|
669
|
-
# Set
|
670
|
-
def hset(key,
|
671
|
-
node_for(key).hset(key,
|
744
|
+
# Set multiple hash fields to multiple values.
|
745
|
+
def hset(key, *attrs)
|
746
|
+
node_for(key).hset(key, *attrs)
|
672
747
|
end
|
673
748
|
|
674
749
|
# Set the value of a hash field, only if the field does not exist.
|
@@ -740,7 +815,7 @@ class Redis
|
|
740
815
|
end
|
741
816
|
|
742
817
|
def subscribed?
|
743
|
-
|
818
|
+
!!@subscribed_node
|
744
819
|
end
|
745
820
|
|
746
821
|
# Listen for messages published to the given channels.
|
@@ -758,7 +833,8 @@ class Redis
|
|
758
833
|
|
759
834
|
# Stop listening for messages posted to the given channels.
|
760
835
|
def unsubscribe(*channels)
|
761
|
-
raise
|
836
|
+
raise "Can't unsubscribe if not subscribed." unless subscribed?
|
837
|
+
|
762
838
|
@subscribed_node.unsubscribe(*channels)
|
763
839
|
end
|
764
840
|
|
@@ -774,13 +850,26 @@ class Redis
|
|
774
850
|
end
|
775
851
|
|
776
852
|
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
777
|
-
def watch(*keys)
|
778
|
-
|
853
|
+
def watch(*keys, &block)
|
854
|
+
ensure_same_node(:watch, keys) do |node|
|
855
|
+
@watch_key = key_tag(keys.first) || keys.first.to_s
|
856
|
+
|
857
|
+
begin
|
858
|
+
node.watch(*keys, &block)
|
859
|
+
rescue StandardError
|
860
|
+
@watch_key = nil
|
861
|
+
raise
|
862
|
+
end
|
863
|
+
end
|
779
864
|
end
|
780
865
|
|
781
866
|
# Forget about all watched keys.
|
782
867
|
def unwatch
|
783
|
-
raise CannotDistribute, :unwatch
|
868
|
+
raise CannotDistribute, :unwatch unless @watch_key
|
869
|
+
|
870
|
+
result = node_for(@watch_key).unwatch
|
871
|
+
@watch_key = nil
|
872
|
+
result
|
784
873
|
end
|
785
874
|
|
786
875
|
def pipelined
|
@@ -788,18 +877,30 @@ class Redis
|
|
788
877
|
end
|
789
878
|
|
790
879
|
# Mark the start of a transaction block.
|
791
|
-
def multi
|
792
|
-
raise CannotDistribute, :multi
|
880
|
+
def multi(&block)
|
881
|
+
raise CannotDistribute, :multi unless @watch_key
|
882
|
+
|
883
|
+
result = node_for(@watch_key).multi(&block)
|
884
|
+
@watch_key = nil if block_given?
|
885
|
+
result
|
793
886
|
end
|
794
887
|
|
795
888
|
# Execute all commands issued after MULTI.
|
796
889
|
def exec
|
797
|
-
raise CannotDistribute, :exec
|
890
|
+
raise CannotDistribute, :exec unless @watch_key
|
891
|
+
|
892
|
+
result = node_for(@watch_key).exec
|
893
|
+
@watch_key = nil
|
894
|
+
result
|
798
895
|
end
|
799
896
|
|
800
897
|
# Discard all commands issued after MULTI.
|
801
898
|
def discard
|
802
|
-
raise CannotDistribute, :discard
|
899
|
+
raise CannotDistribute, :discard unless @watch_key
|
900
|
+
|
901
|
+
result = node_for(@watch_key).discard
|
902
|
+
@watch_key = nil
|
903
|
+
result
|
803
904
|
end
|
804
905
|
|
805
906
|
# Control remote script registry.
|
@@ -858,7 +959,7 @@ class Redis
|
|
858
959
|
self.class.new(@node_configs, @default_options)
|
859
960
|
end
|
860
961
|
|
861
|
-
|
962
|
+
protected
|
862
963
|
|
863
964
|
def on_each_node(command, *args)
|
864
965
|
nodes.map do |node|
|
data/lib/redis/errors.rb
CHANGED
data/lib/redis/hash_ring.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'zlib'
|
2
4
|
|
3
5
|
class Redis
|
4
6
|
class HashRing
|
5
|
-
|
6
7
|
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
7
8
|
|
8
9
|
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
@@ -10,7 +11,7 @@ class Redis
|
|
10
11
|
# nodes is a list of objects that have a proper to_s representation.
|
11
12
|
# replicas indicates how many virtual points should be used pr. node,
|
12
13
|
# replicas are required to improve the distribution.
|
13
|
-
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
def initialize(nodes = [], replicas = POINTS_PER_SERVER)
|
14
15
|
@replicas = replicas
|
15
16
|
@ring = {}
|
16
17
|
@nodes = []
|
@@ -32,11 +33,11 @@ class Redis
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def remove_node(node)
|
35
|
-
@nodes.reject!{|n| n.id == node.id}
|
36
|
+
@nodes.reject! { |n| n.id == node.id }
|
36
37
|
@replicas.times do |i|
|
37
38
|
key = Zlib.crc32("#{node.id}:#{i}")
|
38
39
|
@ring.delete(key)
|
39
|
-
@sorted_keys.reject! {|k| k == key}
|
40
|
+
@sorted_keys.reject! { |k| k == key }
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -46,27 +47,29 @@ class Redis
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def get_node_pos(key)
|
49
|
-
return [nil,nil] if @ring.
|
50
|
+
return [nil, nil] if @ring.empty?
|
51
|
+
|
50
52
|
crc = Zlib.crc32(key)
|
51
53
|
idx = HashRing.binary_search(@sorted_keys, crc)
|
52
|
-
|
54
|
+
[@ring[@sorted_keys[idx]], idx]
|
53
55
|
end
|
54
56
|
|
55
57
|
def iter_nodes(key)
|
56
|
-
return [nil,nil] if @ring.
|
58
|
+
return [nil, nil] if @ring.empty?
|
59
|
+
|
57
60
|
_, pos = get_node_pos(key)
|
58
61
|
@ring.size.times do |n|
|
59
|
-
yield @ring[@sorted_keys[(pos+n) % @ring.size]]
|
62
|
+
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
66
|
# Find the closest index in HashRing with value <= the given value
|
64
|
-
def self.binary_search(ary, value
|
67
|
+
def self.binary_search(ary, value)
|
65
68
|
upper = ary.size - 1
|
66
69
|
lower = 0
|
67
70
|
idx = 0
|
68
71
|
|
69
|
-
while
|
72
|
+
while lower <= upper
|
70
73
|
idx = (lower + upper) / 2
|
71
74
|
comp = ary[idx] <=> value
|
72
75
|
|
@@ -79,10 +82,8 @@ class Redis
|
|
79
82
|
end
|
80
83
|
end
|
81
84
|
|
82
|
-
if upper < 0
|
83
|
-
|
84
|
-
end
|
85
|
-
return upper
|
85
|
+
upper = ary.size - 1 if upper < 0
|
86
|
+
upper
|
86
87
|
end
|
87
88
|
end
|
88
89
|
end
|
data/lib/redis/pipeline.rb
CHANGED
@@ -1,15 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
class Pipeline
|
3
5
|
attr_accessor :db
|
6
|
+
attr_reader :client
|
4
7
|
|
5
8
|
attr :futures
|
6
9
|
|
7
|
-
def initialize
|
10
|
+
def initialize(client)
|
11
|
+
@client = client.is_a?(Pipeline) ? client.client : client
|
8
12
|
@with_reconnect = true
|
9
13
|
@shutdown = false
|
10
14
|
@futures = []
|
11
15
|
end
|
12
16
|
|
17
|
+
def timeout
|
18
|
+
client.timeout
|
19
|
+
end
|
20
|
+
|
13
21
|
def with_reconnect?
|
14
22
|
@with_reconnect
|
15
23
|
end
|
@@ -26,15 +34,19 @@ class Redis
|
|
26
34
|
@futures.empty?
|
27
35
|
end
|
28
36
|
|
29
|
-
def call(command, &block)
|
37
|
+
def call(command, timeout: nil, &block)
|
30
38
|
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
31
39
|
# the connection is gone.
|
32
40
|
@shutdown = true if command.first == :shutdown
|
33
|
-
future = Future.new(command, block)
|
41
|
+
future = Future.new(command, block, timeout)
|
34
42
|
@futures << future
|
35
43
|
future
|
36
44
|
end
|
37
45
|
|
46
|
+
def call_with_timeout(command, timeout, &block)
|
47
|
+
call(command, timeout: timeout, &block)
|
48
|
+
end
|
49
|
+
|
38
50
|
def call_pipeline(pipeline)
|
39
51
|
@shutdown = true if pipeline.shutdown?
|
40
52
|
@futures.concat(pipeline.futures)
|
@@ -43,10 +55,14 @@ class Redis
|
|
43
55
|
end
|
44
56
|
|
45
57
|
def commands
|
46
|
-
@futures.map
|
58
|
+
@futures.map(&:_command)
|
59
|
+
end
|
60
|
+
|
61
|
+
def timeouts
|
62
|
+
@futures.map(&:timeout)
|
47
63
|
end
|
48
64
|
|
49
|
-
def with_reconnect(val=true)
|
65
|
+
def with_reconnect(val = true)
|
50
66
|
@with_reconnect = false unless val
|
51
67
|
yield
|
52
68
|
end
|
@@ -78,7 +94,8 @@ class Redis
|
|
78
94
|
|
79
95
|
if exec.size < futures.size
|
80
96
|
# Some command wasn't recognized by Redis.
|
81
|
-
|
97
|
+
command_error = replies.detect { |r| r.is_a?(CommandError) }
|
98
|
+
raise command_error
|
82
99
|
end
|
83
100
|
|
84
101
|
super(exec) do |reply|
|
@@ -89,6 +106,14 @@ class Redis
|
|
89
106
|
end
|
90
107
|
end
|
91
108
|
|
109
|
+
def timeouts
|
110
|
+
if empty?
|
111
|
+
[]
|
112
|
+
else
|
113
|
+
[nil, *super, nil]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
92
117
|
def commands
|
93
118
|
if empty?
|
94
119
|
[]
|
@@ -108,12 +133,25 @@ class Redis
|
|
108
133
|
class Future < BasicObject
|
109
134
|
FutureNotReady = ::Redis::FutureNotReady.new
|
110
135
|
|
111
|
-
|
136
|
+
attr_reader :timeout
|
137
|
+
|
138
|
+
def initialize(command, transformation, timeout)
|
112
139
|
@command = command
|
113
140
|
@transformation = transformation
|
141
|
+
@timeout = timeout
|
114
142
|
@object = FutureNotReady
|
115
143
|
end
|
116
144
|
|
145
|
+
def ==(_other)
|
146
|
+
message = +"The methods == and != are deprecated for Redis::Future and will be removed in 4.2.0"
|
147
|
+
message << " - You probably meant to call .value == or .value !="
|
148
|
+
message << " (#{::Kernel.caller(1, 1).first})\n"
|
149
|
+
|
150
|
+
::Kernel.warn(message)
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
117
155
|
def inspect
|
118
156
|
"<Redis::Future #{@command.inspect}>"
|
119
157
|
end
|
@@ -128,7 +166,7 @@ class Redis
|
|
128
166
|
end
|
129
167
|
|
130
168
|
def value
|
131
|
-
::Kernel.raise(@object) if @object.
|
169
|
+
::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
|
132
170
|
@object
|
133
171
|
end
|
134
172
|
|