redis-cluster-client 0.7.6 → 0.7.8
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41d87ce45a108ff8ec7d3c17805276d5ba7df11d577de3f7d6b1176bdd297ed0
|
4
|
+
data.tar.gz: 7118539907bb755225ba908cf160ad8af73aeb5204820682ca3fc3a67813f3cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96e3c0f9c2860002642e079b756065e32f04891cd793dce9efcb74bb87a13683b14fee13c039693d99599549fee7ace68d54561b8c730d5e9eb7bb937a82c57d
|
7
|
+
data.tar.gz: 2e512b774b5778e3f7a2a3557bb45d97971d209c787494a19d81e0d8146f5d51bbef7940f37fa38f7587e1037185e23fa27ca8d48a46225ced6640be8425198e
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis_client'
|
4
|
+
require 'redis_client/cluster/key_slot_converter'
|
5
|
+
require 'redis_client/cluster/transaction'
|
6
|
+
|
7
|
+
class RedisClient
|
8
|
+
class Cluster
|
9
|
+
class OptimisticLocking
|
10
|
+
def initialize(router)
|
11
|
+
@router = router
|
12
|
+
end
|
13
|
+
|
14
|
+
def watch(keys)
|
15
|
+
ensure_safe_keys(keys)
|
16
|
+
node = find_node(keys)
|
17
|
+
cnt = 0 # We assume redirects occurred when incrementing it.
|
18
|
+
|
19
|
+
@router.handle_redirection(node, retry_count: 1) do |nd|
|
20
|
+
cnt += 1
|
21
|
+
nd.with do |c|
|
22
|
+
c.call('WATCH', *keys)
|
23
|
+
reply = yield(c, cnt > 1)
|
24
|
+
c.call('UNWATCH')
|
25
|
+
reply
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def ensure_safe_keys(keys)
|
33
|
+
return if safe?(keys)
|
34
|
+
|
35
|
+
raise ::RedisClient::Cluster::Transaction::ConsistencyError, "unsafe watch: #{keys.join(' ')}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def safe?(keys)
|
39
|
+
return false if keys.empty?
|
40
|
+
|
41
|
+
slots = keys.map do |k|
|
42
|
+
return false if k.nil? || k.empty?
|
43
|
+
|
44
|
+
::RedisClient::Cluster::KeySlotConverter.convert(k)
|
45
|
+
end
|
46
|
+
|
47
|
+
slots.uniq.size == 1
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_node(keys)
|
51
|
+
node_key = @router.find_primary_node_key(['WATCH', *keys])
|
52
|
+
return @router.find_node(node_key) unless node_key.nil?
|
53
|
+
|
54
|
+
raise ::RedisClient::Cluster::Transaction::ConsistencyError, "couldn't determine the node"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -2,21 +2,23 @@
|
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
4
|
require 'redis_client/cluster/pipeline'
|
5
|
-
require 'redis_client/cluster/
|
5
|
+
require 'redis_client/cluster/node_key'
|
6
6
|
|
7
7
|
class RedisClient
|
8
8
|
class Cluster
|
9
9
|
class Transaction
|
10
10
|
ConsistencyError = Class.new(::RedisClient::Error)
|
11
|
+
MAX_REDIRECTION = 2
|
11
12
|
|
12
|
-
def initialize(router, command_builder,
|
13
|
+
def initialize(router, command_builder, node: nil, resharding: false)
|
13
14
|
@router = router
|
14
15
|
@command_builder = command_builder
|
15
|
-
@watch = watch
|
16
16
|
@retryable = true
|
17
17
|
@pipeline = ::RedisClient::Pipeline.new(@command_builder)
|
18
|
-
@
|
19
|
-
@node =
|
18
|
+
@pending_commands = []
|
19
|
+
@node = node
|
20
|
+
prepare_tx unless @node.nil?
|
21
|
+
@resharding_state = resharding
|
20
22
|
end
|
21
23
|
|
22
24
|
def call(*command, **kwargs, &block)
|
@@ -24,7 +26,7 @@ class RedisClient
|
|
24
26
|
if prepare(command)
|
25
27
|
@pipeline.call_v(command, &block)
|
26
28
|
else
|
27
|
-
|
29
|
+
defer { @pipeline.call_v(command, &block) }
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
@@ -33,7 +35,7 @@ class RedisClient
|
|
33
35
|
if prepare(command)
|
34
36
|
@pipeline.call_v(command, &block)
|
35
37
|
else
|
36
|
-
|
38
|
+
defer { @pipeline.call_v(command, &block) }
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
@@ -43,7 +45,7 @@ class RedisClient
|
|
43
45
|
if prepare(command)
|
44
46
|
@pipeline.call_once_v(command, &block)
|
45
47
|
else
|
46
|
-
|
48
|
+
defer { @pipeline.call_once_v(command, &block) }
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -53,39 +55,24 @@ class RedisClient
|
|
53
55
|
if prepare(command)
|
54
56
|
@pipeline.call_once_v(command, &block)
|
55
57
|
else
|
56
|
-
|
58
|
+
defer { @pipeline.call_once_v(command, &block) }
|
57
59
|
end
|
58
60
|
end
|
59
61
|
|
60
62
|
def execute
|
61
|
-
@
|
63
|
+
@pending_commands.each(&:call)
|
62
64
|
|
63
65
|
raise ArgumentError, 'empty transaction' if @pipeline._empty?
|
64
66
|
raise ConsistencyError, "couldn't determine the node: #{@pipeline._commands}" if @node.nil?
|
65
|
-
raise ConsistencyError, "unsafe watch: #{@watch.join(' ')}" unless safe_watch?
|
66
67
|
|
67
68
|
settle
|
68
69
|
end
|
69
70
|
|
70
71
|
private
|
71
72
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
def safe_watch?
|
77
|
-
return true unless watch?
|
78
|
-
return false if @node.nil?
|
79
|
-
|
80
|
-
slots = @watch.map do |k|
|
81
|
-
return false if k.nil? || k.empty?
|
82
|
-
|
83
|
-
::RedisClient::Cluster::KeySlotConverter.convert(k)
|
84
|
-
end
|
85
|
-
|
86
|
-
return false if slots.uniq.size != 1
|
87
|
-
|
88
|
-
@router.find_primary_node_by_slot(slots.first) == @node
|
73
|
+
def defer(&block)
|
74
|
+
@pending_commands << block
|
75
|
+
nil
|
89
76
|
end
|
90
77
|
|
91
78
|
def prepare(command)
|
@@ -95,17 +82,19 @@ class RedisClient
|
|
95
82
|
return false if node_key.nil?
|
96
83
|
|
97
84
|
@node = @router.find_node(node_key)
|
98
|
-
|
99
|
-
@pipeline.call('MULTI')
|
100
|
-
@buffer.each(&:call)
|
101
|
-
@buffer.clear
|
85
|
+
prepare_tx
|
102
86
|
true
|
103
87
|
end
|
104
88
|
|
89
|
+
def prepare_tx
|
90
|
+
@pipeline.call('MULTI')
|
91
|
+
@pending_commands.each(&:call)
|
92
|
+
@pending_commands.clear
|
93
|
+
end
|
94
|
+
|
105
95
|
def settle
|
106
96
|
@pipeline.call('EXEC')
|
107
|
-
@
|
108
|
-
send_transaction(@node, redirect: true)
|
97
|
+
send_transaction(@node, redirect: MAX_REDIRECTION)
|
109
98
|
end
|
110
99
|
|
111
100
|
def send_transaction(client, redirect:)
|
@@ -122,17 +111,18 @@ class RedisClient
|
|
122
111
|
client.middlewares.call_pipelined(commands, client.config) do
|
123
112
|
connection.call_pipelined(commands, nil)
|
124
113
|
rescue ::RedisClient::CommandError => e
|
125
|
-
return handle_command_error!(commands, e)
|
114
|
+
return handle_command_error!(client, commands, e, redirect: redirect) unless redirect.zero?
|
126
115
|
|
127
116
|
raise
|
128
117
|
end
|
129
118
|
end
|
130
119
|
|
131
|
-
|
132
|
-
|
120
|
+
return if replies.last.nil?
|
121
|
+
|
122
|
+
coerce_results!(replies.last)
|
133
123
|
end
|
134
124
|
|
135
|
-
def coerce_results!(results, offset)
|
125
|
+
def coerce_results!(results, offset: 1)
|
136
126
|
results.each_with_index do |result, index|
|
137
127
|
if result.is_a?(::RedisClient::CommandError)
|
138
128
|
result._set_command(@pipeline._commands[index + offset])
|
@@ -150,39 +140,30 @@ class RedisClient
|
|
150
140
|
results
|
151
141
|
end
|
152
142
|
|
153
|
-
def handle_command_error!(commands, err)
|
143
|
+
def handle_command_error!(client, commands, err, redirect:) # rubocop:disable Metrics/AbcSize
|
154
144
|
if err.message.start_with?('CROSSSLOT')
|
155
145
|
raise ConsistencyError, "#{err.message}: #{err.command}"
|
156
|
-
elsif err.message.start_with?('MOVED'
|
157
|
-
ensure_the_same_node!(commands)
|
158
|
-
|
146
|
+
elsif err.message.start_with?('MOVED')
|
147
|
+
ensure_the_same_node!(client, commands)
|
148
|
+
node = @router.assign_redirection_node(err.message)
|
149
|
+
send_transaction(node, redirect: redirect - 1)
|
150
|
+
elsif err.message.start_with?('ASK')
|
151
|
+
ensure_the_same_node!(client, commands)
|
152
|
+
node = @router.assign_asking_node(err.message)
|
153
|
+
try_asking(node) ? send_transaction(node, redirect: redirect - 1) : err
|
159
154
|
else
|
160
155
|
raise err
|
161
156
|
end
|
162
157
|
end
|
163
158
|
|
164
|
-
def ensure_the_same_node!(commands)
|
165
|
-
commands.
|
166
|
-
|
167
|
-
next if node_key.nil?
|
159
|
+
def ensure_the_same_node!(client, commands)
|
160
|
+
node_keys = commands.map { |command| @router.find_primary_node_key(command) }.compact.uniq
|
161
|
+
expected_node_key = ::RedisClient::Cluster::NodeKey.build_from_client(client)
|
168
162
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
raise ConsistencyError, "the transaction should be executed to a slot in a node: #{commands}"
|
173
|
-
end
|
174
|
-
end
|
163
|
+
return if !@resharding_state && node_keys.size == 1 && node_keys.first == expected_node_key
|
164
|
+
return if @resharding_state && node_keys.size == 1
|
175
165
|
|
176
|
-
|
177
|
-
if err.message.start_with?('MOVED')
|
178
|
-
node = @router.assign_redirection_node(err.message)
|
179
|
-
send_transaction(node, redirect: false)
|
180
|
-
elsif err.message.start_with?('ASK')
|
181
|
-
node = @router.assign_asking_node(err.message)
|
182
|
-
try_asking(node) ? send_transaction(node, redirect: false) : err
|
183
|
-
else
|
184
|
-
raise err
|
185
|
-
end
|
166
|
+
raise(ConsistencyError, "the transaction should be executed to a slot in a node: #{commands}")
|
186
167
|
end
|
187
168
|
|
188
169
|
def try_asking(node)
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -6,6 +6,7 @@ require 'redis_client/cluster/pub_sub'
|
|
6
6
|
require 'redis_client/cluster/router'
|
7
7
|
require 'redis_client/cluster/transaction'
|
8
8
|
require 'redis_client/cluster/pinning_node'
|
9
|
+
require 'redis_client/cluster/optimistic_locking'
|
9
10
|
|
10
11
|
class RedisClient
|
11
12
|
class Cluster
|
@@ -91,9 +92,19 @@ class RedisClient
|
|
91
92
|
end
|
92
93
|
|
93
94
|
def multi(watch: nil)
|
94
|
-
|
95
|
-
|
96
|
-
|
95
|
+
if watch.nil? || watch.empty?
|
96
|
+
transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder)
|
97
|
+
yield transaction
|
98
|
+
return transaction.execute
|
99
|
+
end
|
100
|
+
|
101
|
+
::RedisClient::Cluster::OptimisticLocking.new(@router).watch(watch) do |c, resharding|
|
102
|
+
transaction = ::RedisClient::Cluster::Transaction.new(
|
103
|
+
@router, @command_builder, node: c, resharding: resharding
|
104
|
+
)
|
105
|
+
yield transaction
|
106
|
+
transaction.execute
|
107
|
+
end
|
97
108
|
end
|
98
109
|
|
99
110
|
def pubsub
|
@@ -102,12 +113,11 @@ class RedisClient
|
|
102
113
|
|
103
114
|
# TODO: This isn't an official public interface yet. Don't use in your production environment.
|
104
115
|
# @see https://github.com/redis-rb/redis-cluster-client/issues/299
|
105
|
-
def with(key: nil, hashtag: nil, write: true
|
116
|
+
def with(key: nil, hashtag: nil, write: true)
|
106
117
|
key = process_with_arguments(key, hashtag)
|
107
|
-
|
108
118
|
node_key = @router.find_node_key_by_key(key, primary: write)
|
109
119
|
node = @router.find_node(node_key)
|
110
|
-
yield ::RedisClient::Cluster::PinningNode.new(
|
120
|
+
node.with { |c| yield ::RedisClient::Cluster::PinningNode.new(c) }
|
111
121
|
end
|
112
122
|
|
113
123
|
def close
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-cluster-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Taishi Kasuga
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- lib/redis_client/cluster/node/random_replica_or_primary.rb
|
50
50
|
- lib/redis_client/cluster/node_key.rb
|
51
51
|
- lib/redis_client/cluster/normalized_cmd_name.rb
|
52
|
+
- lib/redis_client/cluster/optimistic_locking.rb
|
52
53
|
- lib/redis_client/cluster/pinning_node.rb
|
53
54
|
- lib/redis_client/cluster/pipeline.rb
|
54
55
|
- lib/redis_client/cluster/pub_sub.rb
|