redis 4.3.1 → 5.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +190 -0
- data/README.md +132 -154
- data/lib/redis/client.rb +81 -600
- data/lib/redis/commands/bitmaps.rb +73 -0
- data/lib/redis/commands/cluster.rb +28 -0
- data/lib/redis/commands/connection.rb +53 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +258 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +459 -0
- data/lib/redis/commands/lists.rb +339 -0
- data/lib/redis/commands/pubsub.rb +81 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +218 -0
- data/lib/redis/commands/sorted_sets.rb +922 -0
- data/lib/redis/commands/streams.rb +409 -0
- data/lib/redis/commands/strings.rb +314 -0
- data/lib/redis/commands/transactions.rb +115 -0
- data/lib/redis/commands.rb +239 -0
- data/lib/redis/distributed.rb +236 -77
- data/lib/redis/errors.rb +20 -41
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +70 -120
- data/lib/redis/subscribe.rb +50 -14
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +104 -3465
- metadata +27 -57
- data/lib/redis/cluster/command.rb +0 -81
- data/lib/redis/cluster/command_loader.rb +0 -34
- data/lib/redis/cluster/key_slot_converter.rb +0 -72
- data/lib/redis/cluster/node.rb +0 -107
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -37
- data/lib/redis/cluster/option.rb +0 -93
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -49
- data/lib/redis/cluster.rb +0 -295
- data/lib/redis/connection/command_helper.rb +0 -39
- data/lib/redis/connection/hiredis.rb +0 -67
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -427
- data/lib/redis/connection/synchrony.rb +0 -146
- data/lib/redis/connection.rb +0 -11
data/lib/redis/hash_ring.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'zlib'
|
|
4
|
+
require 'digest/md5'
|
|
4
5
|
|
|
5
6
|
class Redis
|
|
6
7
|
class HashRing
|
|
@@ -25,7 +26,7 @@ class Redis
|
|
|
25
26
|
def add_node(node)
|
|
26
27
|
@nodes << node
|
|
27
28
|
@replicas.times do |i|
|
|
28
|
-
key =
|
|
29
|
+
key = server_hash_for("#{node.id}:#{i}")
|
|
29
30
|
@ring[key] = node
|
|
30
31
|
@sorted_keys << key
|
|
31
32
|
end
|
|
@@ -35,7 +36,7 @@ class Redis
|
|
|
35
36
|
def remove_node(node)
|
|
36
37
|
@nodes.reject! { |n| n.id == node.id }
|
|
37
38
|
@replicas.times do |i|
|
|
38
|
-
key =
|
|
39
|
+
key = server_hash_for("#{node.id}:#{i}")
|
|
39
40
|
@ring.delete(key)
|
|
40
41
|
@sorted_keys.reject! { |k| k == key }
|
|
41
42
|
end
|
|
@@ -43,47 +44,46 @@ class Redis
|
|
|
43
44
|
|
|
44
45
|
# get the node in the hash ring for this key
|
|
45
46
|
def get_node(key)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def get_node_pos(key)
|
|
50
|
-
return [nil, nil] if @ring.empty?
|
|
51
|
-
|
|
52
|
-
crc = Zlib.crc32(key)
|
|
53
|
-
idx = HashRing.binary_search(@sorted_keys, crc)
|
|
54
|
-
[@ring[@sorted_keys[idx]], idx]
|
|
47
|
+
hash = hash_for(key)
|
|
48
|
+
idx = binary_search(@sorted_keys, hash)
|
|
49
|
+
@ring[@sorted_keys[idx]]
|
|
55
50
|
end
|
|
56
51
|
|
|
57
52
|
def iter_nodes(key)
|
|
58
53
|
return [nil, nil] if @ring.empty?
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
crc = hash_for(key)
|
|
56
|
+
pos = binary_search(@sorted_keys, crc)
|
|
61
57
|
@ring.size.times do |n|
|
|
62
58
|
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
|
63
59
|
end
|
|
64
60
|
end
|
|
65
61
|
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def hash_for(key)
|
|
65
|
+
Zlib.crc32(key)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def server_hash_for(key)
|
|
69
|
+
Digest::MD5.digest(key).unpack1("L>")
|
|
70
|
+
end
|
|
71
|
+
|
|
66
72
|
# Find the closest index in HashRing with value <= the given value
|
|
67
|
-
def
|
|
68
|
-
upper = ary.size
|
|
73
|
+
def binary_search(ary, value)
|
|
74
|
+
upper = ary.size
|
|
69
75
|
lower = 0
|
|
70
|
-
idx = 0
|
|
71
|
-
|
|
72
|
-
while lower <= upper
|
|
73
|
-
idx = (lower + upper) / 2
|
|
74
|
-
comp = ary[idx] <=> value
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
upper =
|
|
77
|
+
while lower < upper
|
|
78
|
+
mid = (lower + upper) / 2
|
|
79
|
+
if ary[mid] > value
|
|
80
|
+
upper = mid
|
|
80
81
|
else
|
|
81
|
-
lower =
|
|
82
|
+
lower = mid + 1
|
|
82
83
|
end
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
upper
|
|
86
|
-
upper
|
|
86
|
+
upper - 1
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
end
|
data/lib/redis/pipeline.rb
CHANGED
|
@@ -1,126 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
3
5
|
class Redis
|
|
4
|
-
class
|
|
6
|
+
class PipelinedConnection
|
|
5
7
|
attr_accessor :db
|
|
6
|
-
attr_reader :client
|
|
7
|
-
|
|
8
|
-
attr :futures
|
|
9
8
|
|
|
10
|
-
def initialize(
|
|
11
|
-
@
|
|
12
|
-
@
|
|
13
|
-
@
|
|
14
|
-
@futures = []
|
|
9
|
+
def initialize(pipeline, futures = [], exception: true)
|
|
10
|
+
@pipeline = pipeline
|
|
11
|
+
@futures = futures
|
|
12
|
+
@exception = exception
|
|
15
13
|
end
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
client.timeout
|
|
19
|
-
end
|
|
15
|
+
include Commands
|
|
20
16
|
|
|
21
|
-
def
|
|
22
|
-
|
|
17
|
+
def pipelined
|
|
18
|
+
yield self
|
|
23
19
|
end
|
|
24
20
|
|
|
25
|
-
def
|
|
26
|
-
|
|
21
|
+
def multi
|
|
22
|
+
transaction = MultiConnection.new(@pipeline, @futures)
|
|
23
|
+
send_command([:multi])
|
|
24
|
+
size = @futures.size
|
|
25
|
+
yield transaction
|
|
26
|
+
multi_future = MultiFuture.new(@futures[size..-1])
|
|
27
|
+
@pipeline.call_v([:exec]) do |result|
|
|
28
|
+
multi_future._set(result)
|
|
29
|
+
end
|
|
30
|
+
@futures << multi_future
|
|
31
|
+
multi_future
|
|
27
32
|
end
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
@shutdown
|
|
31
|
-
end
|
|
34
|
+
private
|
|
32
35
|
|
|
33
|
-
def
|
|
34
|
-
|
|
36
|
+
def synchronize
|
|
37
|
+
yield self
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
def send_command(command, &block)
|
|
41
|
+
future = Future.new(command, block, @exception)
|
|
42
|
+
@pipeline.call_v(command) do |result|
|
|
43
|
+
future._set(result)
|
|
44
|
+
end
|
|
42
45
|
@futures << future
|
|
43
46
|
future
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def call_pipeline(pipeline)
|
|
51
|
-
@shutdown = true if pipeline.shutdown?
|
|
52
|
-
@futures.concat(pipeline.futures)
|
|
53
|
-
@db = pipeline.db
|
|
54
|
-
nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def commands
|
|
58
|
-
@futures.map(&:_command)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def timeouts
|
|
62
|
-
@futures.map(&:timeout)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def with_reconnect(val = true)
|
|
66
|
-
@with_reconnect = false unless val
|
|
67
|
-
yield
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def without_reconnect(&blk)
|
|
71
|
-
with_reconnect(false, &blk)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def finish(replies, &blk)
|
|
75
|
-
if blk
|
|
76
|
-
futures.each_with_index.map do |future, i|
|
|
77
|
-
future._set(blk.call(replies[i]))
|
|
78
|
-
end
|
|
79
|
-
else
|
|
80
|
-
futures.each_with_index.map do |future, i|
|
|
81
|
-
future._set(replies[i])
|
|
82
|
-
end
|
|
49
|
+
def send_blocking_command(command, timeout, &block)
|
|
50
|
+
future = Future.new(command, block, @exception)
|
|
51
|
+
@pipeline.blocking_call_v(timeout, command) do |result|
|
|
52
|
+
future._set(result)
|
|
83
53
|
end
|
|
54
|
+
@futures << future
|
|
55
|
+
future
|
|
84
56
|
end
|
|
57
|
+
end
|
|
85
58
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return if exec.nil? # The transaction failed because of WATCH.
|
|
91
|
-
|
|
92
|
-
# EXEC command failed.
|
|
93
|
-
raise exec if exec.is_a?(CommandError)
|
|
94
|
-
|
|
95
|
-
if exec.size < futures.size
|
|
96
|
-
# Some command wasn't recognized by Redis.
|
|
97
|
-
command_error = replies.detect { |r| r.is_a?(CommandError) }
|
|
98
|
-
raise command_error
|
|
99
|
-
end
|
|
59
|
+
class MultiConnection < PipelinedConnection
|
|
60
|
+
def multi
|
|
61
|
+
raise Redis::BaseError, "Can't nest multi transaction"
|
|
62
|
+
end
|
|
100
63
|
|
|
101
|
-
|
|
102
|
-
# Because an EXEC returns nested replies, hiredis won't be able to
|
|
103
|
-
# convert an error reply to a CommandError instance itself. This is
|
|
104
|
-
# specific to MULTI/EXEC, so we solve this here.
|
|
105
|
-
reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
|
|
106
|
-
end
|
|
107
|
-
end
|
|
64
|
+
private
|
|
108
65
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def commands
|
|
118
|
-
if empty?
|
|
119
|
-
[]
|
|
120
|
-
else
|
|
121
|
-
[[:multi]] + super + [[:exec]]
|
|
122
|
-
end
|
|
123
|
-
end
|
|
66
|
+
# Blocking commands inside transaction behave like non-blocking.
|
|
67
|
+
# It shouldn't be done though.
|
|
68
|
+
# https://redis.io/commands/blpop/#blpop-inside-a-multi--exec-transaction
|
|
69
|
+
def send_blocking_command(command, _timeout, &block)
|
|
70
|
+
send_command(command, &block)
|
|
124
71
|
end
|
|
125
72
|
end
|
|
126
73
|
|
|
@@ -133,23 +80,11 @@ class Redis
|
|
|
133
80
|
class Future < BasicObject
|
|
134
81
|
FutureNotReady = ::Redis::FutureNotReady.new
|
|
135
82
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def initialize(command, transformation, timeout)
|
|
83
|
+
def initialize(command, coerce, exception)
|
|
139
84
|
@command = command
|
|
140
|
-
@transformation = transformation
|
|
141
|
-
@timeout = timeout
|
|
142
85
|
@object = FutureNotReady
|
|
143
|
-
|
|
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
|
|
86
|
+
@coerce = coerce
|
|
87
|
+
@exception = exception
|
|
153
88
|
end
|
|
154
89
|
|
|
155
90
|
def inspect
|
|
@@ -157,16 +92,12 @@ class Redis
|
|
|
157
92
|
end
|
|
158
93
|
|
|
159
94
|
def _set(object)
|
|
160
|
-
@object = @
|
|
95
|
+
@object = @coerce ? @coerce.call(object) : object
|
|
161
96
|
value
|
|
162
97
|
end
|
|
163
98
|
|
|
164
|
-
def _command
|
|
165
|
-
@command
|
|
166
|
-
end
|
|
167
|
-
|
|
168
99
|
def value
|
|
169
|
-
::Kernel.raise(@object) if @object.is_a?(::
|
|
100
|
+
::Kernel.raise(@object) if @exception && @object.is_a?(::StandardError)
|
|
170
101
|
@object
|
|
171
102
|
end
|
|
172
103
|
|
|
@@ -178,4 +109,23 @@ class Redis
|
|
|
178
109
|
Future
|
|
179
110
|
end
|
|
180
111
|
end
|
|
112
|
+
|
|
113
|
+
class MultiFuture < Future
|
|
114
|
+
def initialize(futures)
|
|
115
|
+
@futures = futures
|
|
116
|
+
@command = [:exec]
|
|
117
|
+
@object = FutureNotReady
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def _set(replies)
|
|
121
|
+
@object = if replies
|
|
122
|
+
@futures.map.with_index do |future, index|
|
|
123
|
+
future._set(replies[index])
|
|
124
|
+
future.value
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
replies
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
181
131
|
end
|
data/lib/redis/subscribe.rb
CHANGED
|
@@ -4,10 +4,13 @@ class Redis
|
|
|
4
4
|
class SubscribedClient
|
|
5
5
|
def initialize(client)
|
|
6
6
|
@client = client
|
|
7
|
+
@write_monitor = Monitor.new
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
def
|
|
10
|
-
@
|
|
10
|
+
def call_v(command)
|
|
11
|
+
@write_monitor.synchronize do
|
|
12
|
+
@client.call_v(command)
|
|
13
|
+
end
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def subscribe(*channels, &block)
|
|
@@ -26,12 +29,28 @@ class Redis
|
|
|
26
29
|
subscription("psubscribe", "punsubscribe", channels, block, timeout)
|
|
27
30
|
end
|
|
28
31
|
|
|
32
|
+
def ssubscribe(*channels, &block)
|
|
33
|
+
subscription("ssubscribe", "sunsubscribe", channels, block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ssubscribe_with_timeout(timeout, *channels, &block)
|
|
37
|
+
subscription("ssubscribe", "sunsubscribe", channels, block, timeout)
|
|
38
|
+
end
|
|
39
|
+
|
|
29
40
|
def unsubscribe(*channels)
|
|
30
|
-
|
|
41
|
+
call_v([:unsubscribe, *channels])
|
|
31
42
|
end
|
|
32
43
|
|
|
33
44
|
def punsubscribe(*channels)
|
|
34
|
-
|
|
45
|
+
call_v([:punsubscribe, *channels])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sunsubscribe(*channels)
|
|
49
|
+
call_v([:sunsubscribe, *channels])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def close
|
|
53
|
+
@client.close
|
|
35
54
|
end
|
|
36
55
|
|
|
37
56
|
protected
|
|
@@ -39,13 +58,21 @@ class Redis
|
|
|
39
58
|
def subscription(start, stop, channels, block, timeout = 0)
|
|
40
59
|
sub = Subscription.new(&block)
|
|
41
60
|
|
|
42
|
-
|
|
61
|
+
case start
|
|
62
|
+
when "ssubscribe" then channels.each { |c| call_v([start, c]) } # avoid cross-slot keys
|
|
63
|
+
else call_v([start, *channels])
|
|
64
|
+
end
|
|
43
65
|
|
|
44
|
-
@client.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
while event = @client.next_event(timeout)
|
|
67
|
+
if event.is_a?(::RedisClient::CommandError)
|
|
68
|
+
raise Client::ERROR_MAPPING.fetch(event.class), event.message
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
type, *rest = event
|
|
72
|
+
if callback = sub.callbacks[type]
|
|
73
|
+
callback.call(*rest)
|
|
74
|
+
end
|
|
75
|
+
break if type == stop && rest.last == 0
|
|
49
76
|
end
|
|
50
77
|
# No need to unsubscribe here. The real client closes the connection
|
|
51
78
|
# whenever an exception is raised (see #ensure_connected).
|
|
@@ -56,10 +83,7 @@ class Redis
|
|
|
56
83
|
attr :callbacks
|
|
57
84
|
|
|
58
85
|
def initialize
|
|
59
|
-
@callbacks =
|
|
60
|
-
hash[key] = ->(*_) {}
|
|
61
|
-
end
|
|
62
|
-
|
|
86
|
+
@callbacks = {}
|
|
63
87
|
yield(self)
|
|
64
88
|
end
|
|
65
89
|
|
|
@@ -86,5 +110,17 @@ class Redis
|
|
|
86
110
|
def pmessage(&block)
|
|
87
111
|
@callbacks["pmessage"] = block
|
|
88
112
|
end
|
|
113
|
+
|
|
114
|
+
def ssubscribe(&block)
|
|
115
|
+
@callbacks["ssubscribe"] = block
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def sunsubscribe(&block)
|
|
119
|
+
@callbacks["sunsubscribe"] = block
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def smessage(&block)
|
|
123
|
+
@callbacks["smessage"] = block
|
|
124
|
+
end
|
|
89
125
|
end
|
|
90
126
|
end
|
data/lib/redis/version.rb
CHANGED