redis-cluster-client 0.7.11 → 0.8.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/lib/redis_client/cluster/error_identification.rb +7 -1
- data/lib/redis_client/cluster/node.rb +2 -9
- data/lib/redis_client/cluster/optimistic_locking.rb +36 -6
- data/lib/redis_client/cluster/pipeline.rb +17 -10
- data/lib/redis_client/cluster/router.rb +8 -5
- data/lib/redis_client/cluster/transaction.rb +10 -6
- data/lib/redis_client/cluster.rb +11 -4
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53d1591b89859ddc92dc6ca349ff211d0a6ac14c68ad72e920cc9725d82587b4
|
4
|
+
data.tar.gz: fcfeceffddc9201f8b8c5d6999d3e7aa8581c9f24f0d823b029a5561ef82887d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d51aefc48e64ef0cdbded8b5c8e4b005dcfd103d9fa4ecfa0dff1d9aa4fed5d6f6aefd4dca4ac53e89867b5276812bc6c7f8cf3cb41c1184bd9d8f19ea601471
|
7
|
+
data.tar.gz: 548da631726b4881f0a7712186f656733cb6941aef9ca3415498a6f048f59170fb74a59c30da68d655cee5e5773732d9d781c489f6cda2f0c92a341f1b28b892
|
@@ -4,7 +4,13 @@ class RedisClient
|
|
4
4
|
class Cluster
|
5
5
|
module ErrorIdentification
|
6
6
|
def self.client_owns_error?(err, client)
|
7
|
-
|
7
|
+
return true unless identifiable?(err)
|
8
|
+
|
9
|
+
err.from?(client)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.identifiable?(err)
|
13
|
+
err.is_a?(TaggedError)
|
8
14
|
end
|
9
15
|
|
10
16
|
module TaggedError
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
4
|
require 'redis_client/config'
|
5
|
-
require 'redis_client/cluster/error_identification'
|
6
5
|
require 'redis_client/cluster/errors'
|
7
6
|
require 'redis_client/cluster/node/primary_only'
|
8
7
|
require 'redis_client/cluster/node/random_replica'
|
@@ -79,11 +78,9 @@ class RedisClient
|
|
79
78
|
end
|
80
79
|
|
81
80
|
class Config < ::RedisClient::Config
|
82
|
-
def initialize(scale_read: false,
|
81
|
+
def initialize(scale_read: false, **kwargs)
|
83
82
|
@scale_read = scale_read
|
84
|
-
|
85
|
-
middlewares.unshift ErrorIdentification::Middleware
|
86
|
-
super(middlewares: middlewares, **kwargs)
|
83
|
+
super(**kwargs)
|
87
84
|
end
|
88
85
|
|
89
86
|
private
|
@@ -217,10 +214,6 @@ class RedisClient
|
|
217
214
|
end
|
218
215
|
end
|
219
216
|
|
220
|
-
def owns_error?(err)
|
221
|
-
any? { |c| ErrorIdentification.client_owns_error?(err, c) }
|
222
|
-
end
|
223
|
-
|
224
217
|
private
|
225
218
|
|
226
219
|
def make_topology_class(with_replica, replica_affinity)
|
@@ -8,26 +8,56 @@ class RedisClient
|
|
8
8
|
class OptimisticLocking
|
9
9
|
def initialize(router)
|
10
10
|
@router = router
|
11
|
+
@asking = false
|
11
12
|
end
|
12
13
|
|
13
14
|
def watch(keys)
|
14
15
|
slot = find_slot(keys)
|
15
16
|
raise ::RedisClient::Cluster::Transaction::ConsistencyError, "unsafe watch: #{keys.join(' ')}" if slot.nil?
|
16
17
|
|
18
|
+
# We have not yet selected a node for this transaction, initially, which means we can handle
|
19
|
+
# redirections freely initially (i.e. for the first WATCH call)
|
17
20
|
node = @router.find_primary_node_by_slot(slot)
|
18
|
-
|
21
|
+
handle_redirection(node, retry_count: 1) do |nd|
|
19
22
|
nd.with do |c|
|
20
|
-
c.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
c.ensure_connected_cluster_scoped(retryable: false) do
|
24
|
+
c.call('ASKING') if @asking
|
25
|
+
c.call('WATCH', *keys)
|
26
|
+
begin
|
27
|
+
yield(c, slot, @asking)
|
28
|
+
rescue ::RedisClient::ConnectionError
|
29
|
+
# No need to unwatch on a connection error.
|
30
|
+
raise
|
31
|
+
rescue StandardError
|
32
|
+
c.call('UNWATCH')
|
33
|
+
raise
|
34
|
+
end
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
27
38
|
end
|
28
39
|
|
29
40
|
private
|
30
41
|
|
42
|
+
def handle_redirection(node, retry_count: 1, &blk)
|
43
|
+
@router.handle_redirection(node, retry_count: retry_count) do |nd|
|
44
|
+
handle_asking_once(nd, &blk)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_asking_once(node)
|
49
|
+
yield node
|
50
|
+
rescue ::RedisClient::CommandError => e
|
51
|
+
raise unless ErrorIdentification.client_owns_error?(e, node)
|
52
|
+
raise unless e.message.start_with?('ASK')
|
53
|
+
|
54
|
+
node = @router.assign_asking_node(e.message)
|
55
|
+
@asking = true
|
56
|
+
yield node
|
57
|
+
ensure
|
58
|
+
@asking = false
|
59
|
+
end
|
60
|
+
|
31
61
|
def find_slot(keys)
|
32
62
|
return if keys.empty?
|
33
63
|
return if keys.any? { |k| k.nil? || k.empty? }
|
@@ -12,7 +12,7 @@ class RedisClient
|
|
12
12
|
class Extended < ::RedisClient::Pipeline
|
13
13
|
attr_reader :outer_indices
|
14
14
|
|
15
|
-
def initialize(
|
15
|
+
def initialize(...)
|
16
16
|
super
|
17
17
|
@outer_indices = nil
|
18
18
|
end
|
@@ -50,14 +50,14 @@ class RedisClient
|
|
50
50
|
end
|
51
51
|
|
52
52
|
::RedisClient::ConnectionMixin.module_eval do
|
53
|
-
def call_pipelined_aware_of_redirection(commands, timeouts) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
53
|
+
def call_pipelined_aware_of_redirection(commands, timeouts, exception:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
54
54
|
size = commands.size
|
55
55
|
results = Array.new(commands.size)
|
56
56
|
@pending_reads += size
|
57
57
|
write_multi(commands)
|
58
58
|
|
59
59
|
redirection_indices = nil
|
60
|
-
|
60
|
+
first_exception = nil
|
61
61
|
size.times do |index|
|
62
62
|
timeout = timeouts && timeouts[index]
|
63
63
|
result = read(timeout)
|
@@ -67,14 +67,14 @@ class RedisClient
|
|
67
67
|
if result.is_a?(::RedisClient::CommandError) && result.message.start_with?('MOVED', 'ASK')
|
68
68
|
redirection_indices ||= []
|
69
69
|
redirection_indices << index
|
70
|
-
|
71
|
-
|
70
|
+
elsif exception
|
71
|
+
first_exception ||= result
|
72
72
|
end
|
73
73
|
end
|
74
74
|
results[index] = result
|
75
75
|
end
|
76
76
|
|
77
|
-
raise
|
77
|
+
raise first_exception if exception && first_exception
|
78
78
|
return results if redirection_indices.nil?
|
79
79
|
|
80
80
|
err = ::RedisClient::Cluster::Pipeline::RedirectionNeeded.new
|
@@ -98,10 +98,11 @@ class RedisClient
|
|
98
98
|
attr_accessor :replies, :indices
|
99
99
|
end
|
100
100
|
|
101
|
-
def initialize(router, command_builder, concurrent_worker, seed: Random.new_seed)
|
101
|
+
def initialize(router, command_builder, concurrent_worker, exception:, seed: Random.new_seed)
|
102
102
|
@router = router
|
103
103
|
@command_builder = command_builder
|
104
104
|
@concurrent_worker = concurrent_worker
|
105
|
+
@exception = exception
|
105
106
|
@seed = seed
|
106
107
|
@pipelines = nil
|
107
108
|
@size = 0
|
@@ -212,7 +213,7 @@ class RedisClient
|
|
212
213
|
results = client.ensure_connected_cluster_scoped(retryable: pipeline._retryable?) do |connection|
|
213
214
|
commands = pipeline._commands
|
214
215
|
client.middlewares.call_pipelined(commands, client.config) do
|
215
|
-
connection.call_pipelined_aware_of_redirection(commands, pipeline._timeouts)
|
216
|
+
connection.call_pipelined_aware_of_redirection(commands, pipeline._timeouts, exception: @exception)
|
216
217
|
end
|
217
218
|
end
|
218
219
|
|
@@ -224,15 +225,21 @@ class RedisClient
|
|
224
225
|
|
225
226
|
if err.message.start_with?('MOVED')
|
226
227
|
node = @router.assign_redirection_node(err.message)
|
227
|
-
|
228
|
+
try_redirection(node, pipeline, inner_index)
|
228
229
|
elsif err.message.start_with?('ASK')
|
229
230
|
node = @router.assign_asking_node(err.message)
|
230
|
-
try_asking(node) ?
|
231
|
+
try_asking(node) ? try_redirection(node, pipeline, inner_index) : err
|
231
232
|
else
|
232
233
|
err
|
233
234
|
end
|
234
235
|
end
|
235
236
|
|
237
|
+
def try_redirection(node, pipeline, inner_index)
|
238
|
+
redirect_command(node, pipeline, inner_index)
|
239
|
+
rescue StandardError => e
|
240
|
+
@exception ? raise : e
|
241
|
+
end
|
242
|
+
|
236
243
|
def redirect_command(node, pipeline, inner_index)
|
237
244
|
method = pipeline.get_callee_method(inner_index)
|
238
245
|
command = pipeline.get_command(inner_index)
|
@@ -10,6 +10,7 @@ require 'redis_client/cluster/node_key'
|
|
10
10
|
require 'redis_client/cluster/normalized_cmd_name'
|
11
11
|
require 'redis_client/cluster/transaction'
|
12
12
|
require 'redis_client/cluster/optimistic_locking'
|
13
|
+
require 'redis_client/cluster/error_identification'
|
13
14
|
|
14
15
|
class RedisClient
|
15
16
|
class Cluster
|
@@ -68,7 +69,9 @@ class RedisClient
|
|
68
69
|
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
69
70
|
|
70
71
|
update_cluster_info! if e.errors.values.any? do |err|
|
71
|
-
|
72
|
+
next false if ::RedisClient::Cluster::ErrorIdentification.identifiable?(err) && @node.none? { |c| ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(err, c) }
|
73
|
+
|
74
|
+
err.message.start_with?('CLUSTERDOWN Hash slot not served')
|
72
75
|
end
|
73
76
|
|
74
77
|
raise
|
@@ -97,7 +100,7 @@ class RedisClient
|
|
97
100
|
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
98
101
|
raise
|
99
102
|
rescue ::RedisClient::CommandError => e
|
100
|
-
raise unless ErrorIdentification.client_owns_error?(e, node)
|
103
|
+
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
|
101
104
|
|
102
105
|
if e.message.start_with?('MOVED')
|
103
106
|
node = assign_redirection_node(e.message)
|
@@ -117,7 +120,7 @@ class RedisClient
|
|
117
120
|
end
|
118
121
|
raise
|
119
122
|
rescue ::RedisClient::ConnectionError => e
|
120
|
-
raise unless ErrorIdentification.client_owns_error?(e, node)
|
123
|
+
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
|
121
124
|
|
122
125
|
update_cluster_info!
|
123
126
|
|
@@ -315,9 +318,9 @@ class RedisClient
|
|
315
318
|
def send_watch_command(command)
|
316
319
|
raise ::RedisClient::Cluster::Transaction::ConsistencyError, 'A block required. And you need to use the block argument as a client for the transaction.' unless block_given?
|
317
320
|
|
318
|
-
::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot|
|
321
|
+
::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
|
319
322
|
transaction = ::RedisClient::Cluster::Transaction.new(
|
320
|
-
self, @command_builder, node: c, slot: slot
|
323
|
+
self, @command_builder, node: c, slot: slot, asking: asking
|
321
324
|
)
|
322
325
|
yield transaction
|
323
326
|
transaction.execute
|
@@ -9,7 +9,7 @@ class RedisClient
|
|
9
9
|
ConsistencyError = Class.new(::RedisClient::Error)
|
10
10
|
MAX_REDIRECTION = 2
|
11
11
|
|
12
|
-
def initialize(router, command_builder, node: nil, slot: nil)
|
12
|
+
def initialize(router, command_builder, node: nil, slot: nil, asking: false)
|
13
13
|
@router = router
|
14
14
|
@command_builder = command_builder
|
15
15
|
@retryable = true
|
@@ -18,6 +18,7 @@ class RedisClient
|
|
18
18
|
@node = node
|
19
19
|
prepare_tx unless @node.nil?
|
20
20
|
@watching_slot = slot
|
21
|
+
@asking = asking
|
21
22
|
end
|
22
23
|
|
23
24
|
def call(*command, **kwargs, &block)
|
@@ -93,7 +94,11 @@ class RedisClient
|
|
93
94
|
|
94
95
|
def settle
|
95
96
|
@pipeline.call('EXEC')
|
96
|
-
|
97
|
+
# If we needed ASKING on the watch, we need ASKING on the multi as well.
|
98
|
+
@node.call('ASKING') if @asking
|
99
|
+
# Don't handle redirections at this level if we're in a watch (the watcher handles redirections
|
100
|
+
# at the whole-transaction level.)
|
101
|
+
send_transaction(@node, redirect: !!@watching_slot ? 0 : MAX_REDIRECTION)
|
97
102
|
end
|
98
103
|
|
99
104
|
def send_transaction(client, redirect:)
|
@@ -110,7 +115,8 @@ class RedisClient
|
|
110
115
|
client.middlewares.call_pipelined(commands, client.config) do
|
111
116
|
connection.call_pipelined(commands, nil)
|
112
117
|
rescue ::RedisClient::CommandError => e
|
113
|
-
|
118
|
+
ensure_the_same_slot!(commands)
|
119
|
+
return handle_command_error!(e, redirect: redirect) unless redirect.zero?
|
114
120
|
|
115
121
|
raise
|
116
122
|
end
|
@@ -139,15 +145,13 @@ class RedisClient
|
|
139
145
|
results
|
140
146
|
end
|
141
147
|
|
142
|
-
def handle_command_error!(
|
148
|
+
def handle_command_error!(err, redirect:) # rubocop:disable Metrics/AbcSize
|
143
149
|
if err.message.start_with?('CROSSSLOT')
|
144
150
|
raise ConsistencyError, "#{err.message}: #{err.command}"
|
145
151
|
elsif err.message.start_with?('MOVED')
|
146
|
-
ensure_the_same_slot!(commands)
|
147
152
|
node = @router.assign_redirection_node(err.message)
|
148
153
|
send_transaction(node, redirect: redirect - 1)
|
149
154
|
elsif err.message.start_with?('ASK')
|
150
|
-
ensure_the_same_slot!(commands)
|
151
155
|
node = @router.assign_asking_node(err.message)
|
152
156
|
try_asking(node) ? send_transaction(node, redirect: redirect - 1) : err
|
153
157
|
else
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -82,9 +82,16 @@ class RedisClient
|
|
82
82
|
@router.try_delegate(node, :zscan, key, *args, **kwargs, &block)
|
83
83
|
end
|
84
84
|
|
85
|
-
def pipelined
|
85
|
+
def pipelined(exception: true)
|
86
86
|
seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
|
87
|
-
pipeline = ::RedisClient::Cluster::Pipeline.new(
|
87
|
+
pipeline = ::RedisClient::Cluster::Pipeline.new(
|
88
|
+
@router,
|
89
|
+
@command_builder,
|
90
|
+
@concurrent_worker,
|
91
|
+
exception: exception,
|
92
|
+
seed: seed
|
93
|
+
)
|
94
|
+
|
88
95
|
yield pipeline
|
89
96
|
return [] if pipeline.empty?
|
90
97
|
|
@@ -98,9 +105,9 @@ class RedisClient
|
|
98
105
|
return transaction.execute
|
99
106
|
end
|
100
107
|
|
101
|
-
::RedisClient::Cluster::OptimisticLocking.new(@router).watch(watch) do |c, slot|
|
108
|
+
::RedisClient::Cluster::OptimisticLocking.new(@router).watch(watch) do |c, slot, asking|
|
102
109
|
transaction = ::RedisClient::Cluster::Transaction.new(
|
103
|
-
@router, @command_builder, node: c, slot: slot
|
110
|
+
@router, @command_builder, node: c, slot: slot, asking: asking
|
104
111
|
)
|
105
112
|
yield transaction
|
106
113
|
transaction.execute
|
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.
|
4
|
+
version: 0.8.1
|
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-
|
11
|
+
date: 2024-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.22'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.22'
|
27
27
|
description:
|
28
28
|
email:
|
29
29
|
- proxy0721@gmail.com
|