redis 5.3.0b4__py3-none-any.whl → 6.0.0__py3-none-any.whl
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.
- redis/__init__.py +2 -11
- redis/_parsers/base.py +14 -2
- redis/_parsers/resp3.py +2 -2
- redis/asyncio/client.py +103 -83
- redis/asyncio/cluster.py +147 -102
- redis/asyncio/connection.py +77 -24
- redis/asyncio/lock.py +26 -5
- redis/asyncio/retry.py +12 -0
- redis/asyncio/sentinel.py +11 -1
- redis/asyncio/utils.py +1 -1
- redis/auth/token.py +6 -2
- redis/backoff.py +15 -0
- redis/client.py +160 -138
- redis/cluster.py +211 -82
- redis/commands/cluster.py +1 -11
- redis/commands/core.py +219 -207
- redis/commands/helpers.py +19 -76
- redis/commands/json/__init__.py +1 -1
- redis/commands/redismodules.py +5 -17
- redis/commands/search/aggregation.py +3 -1
- redis/commands/search/commands.py +43 -16
- redis/commands/search/dialect.py +3 -0
- redis/commands/search/profile_information.py +14 -0
- redis/commands/search/query.py +5 -1
- redis/commands/timeseries/__init__.py +1 -1
- redis/commands/vectorset/__init__.py +46 -0
- redis/commands/vectorset/commands.py +367 -0
- redis/commands/vectorset/utils.py +94 -0
- redis/connection.py +89 -33
- redis/exceptions.py +4 -1
- redis/lock.py +24 -4
- redis/ocsp.py +2 -1
- redis/retry.py +12 -0
- redis/sentinel.py +3 -1
- redis/typing.py +1 -1
- redis/utils.py +114 -1
- {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info}/METADATA +57 -23
- redis-6.0.0.dist-info/RECORD +78 -0
- {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info}/WHEEL +1 -2
- redis/commands/graph/__init__.py +0 -263
- redis/commands/graph/commands.py +0 -313
- redis/commands/graph/edge.py +0 -91
- redis/commands/graph/exceptions.py +0 -3
- redis/commands/graph/execution_plan.py +0 -211
- redis/commands/graph/node.py +0 -88
- redis/commands/graph/path.py +0 -78
- redis/commands/graph/query_result.py +0 -588
- redis-5.3.0b4.dist-info/RECORD +0 -82
- redis-5.3.0b4.dist-info/top_level.txt +0 -1
- /redis/commands/search/{indexDefinition.py → index_definition.py} +0 -0
- {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info/licenses}/LICENSE +0 -0
redis/asyncio/cluster.py
CHANGED
|
@@ -2,7 +2,6 @@ import asyncio
|
|
|
2
2
|
import collections
|
|
3
3
|
import random
|
|
4
4
|
import socket
|
|
5
|
-
import ssl
|
|
6
5
|
import warnings
|
|
7
6
|
from typing import (
|
|
8
7
|
Any,
|
|
@@ -26,11 +25,11 @@ from redis._parsers.helpers import (
|
|
|
26
25
|
_RedisCallbacksRESP3,
|
|
27
26
|
)
|
|
28
27
|
from redis.asyncio.client import ResponseCallbackT
|
|
29
|
-
from redis.asyncio.connection import Connection,
|
|
28
|
+
from redis.asyncio.connection import Connection, SSLConnection, parse_url
|
|
30
29
|
from redis.asyncio.lock import Lock
|
|
31
30
|
from redis.asyncio.retry import Retry
|
|
32
31
|
from redis.auth.token import TokenInterface
|
|
33
|
-
from redis.backoff import
|
|
32
|
+
from redis.backoff import ExponentialWithJitterBackoff, NoBackoff
|
|
34
33
|
from redis.client import EMPTY_RESPONSE, NEVER_DECODE, AbstractRedis
|
|
35
34
|
from redis.cluster import (
|
|
36
35
|
PIPELINE_BLOCKED_COMMANDS,
|
|
@@ -39,6 +38,7 @@ from redis.cluster import (
|
|
|
39
38
|
SLOT_ID,
|
|
40
39
|
AbstractRedisCluster,
|
|
41
40
|
LoadBalancer,
|
|
41
|
+
LoadBalancingStrategy,
|
|
42
42
|
block_pipeline_command,
|
|
43
43
|
get_node_name,
|
|
44
44
|
parse_cluster_slots,
|
|
@@ -50,12 +50,10 @@ from redis.event import AfterAsyncClusterInstantiationEvent, EventDispatcher
|
|
|
50
50
|
from redis.exceptions import (
|
|
51
51
|
AskError,
|
|
52
52
|
BusyLoadingError,
|
|
53
|
-
ClusterCrossSlotError,
|
|
54
53
|
ClusterDownError,
|
|
55
54
|
ClusterError,
|
|
56
55
|
ConnectionError,
|
|
57
56
|
DataError,
|
|
58
|
-
MasterDownError,
|
|
59
57
|
MaxConnectionsError,
|
|
60
58
|
MovedError,
|
|
61
59
|
RedisClusterException,
|
|
@@ -67,32 +65,26 @@ from redis.exceptions import (
|
|
|
67
65
|
)
|
|
68
66
|
from redis.typing import AnyKeyT, EncodableT, KeyT
|
|
69
67
|
from redis.utils import (
|
|
68
|
+
SSL_AVAILABLE,
|
|
69
|
+
deprecated_args,
|
|
70
70
|
deprecated_function,
|
|
71
|
-
dict_merge,
|
|
72
71
|
get_lib_version,
|
|
73
72
|
safe_str,
|
|
74
73
|
str_if_bytes,
|
|
74
|
+
truncate_text,
|
|
75
75
|
)
|
|
76
76
|
|
|
77
|
+
if SSL_AVAILABLE:
|
|
78
|
+
from ssl import TLSVersion, VerifyMode
|
|
79
|
+
else:
|
|
80
|
+
TLSVersion = None
|
|
81
|
+
VerifyMode = None
|
|
82
|
+
|
|
77
83
|
TargetNodesT = TypeVar(
|
|
78
84
|
"TargetNodesT", str, "ClusterNode", List["ClusterNode"], Dict[Any, "ClusterNode"]
|
|
79
85
|
)
|
|
80
86
|
|
|
81
87
|
|
|
82
|
-
class ClusterParser(DefaultParser):
|
|
83
|
-
EXCEPTION_CLASSES = dict_merge(
|
|
84
|
-
DefaultParser.EXCEPTION_CLASSES,
|
|
85
|
-
{
|
|
86
|
-
"ASK": AskError,
|
|
87
|
-
"CLUSTERDOWN": ClusterDownError,
|
|
88
|
-
"CROSSSLOT": ClusterCrossSlotError,
|
|
89
|
-
"MASTERDOWN": MasterDownError,
|
|
90
|
-
"MOVED": MovedError,
|
|
91
|
-
"TRYAGAIN": TryAgainError,
|
|
92
|
-
},
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
|
|
96
88
|
class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommands):
|
|
97
89
|
"""
|
|
98
90
|
Create a new RedisCluster client.
|
|
@@ -133,9 +125,15 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
133
125
|
| See:
|
|
134
126
|
https://redis.io/docs/manual/scaling/#redis-cluster-configuration-parameters
|
|
135
127
|
:param read_from_replicas:
|
|
136
|
-
|
|
|
128
|
+
| @deprecated - please use load_balancing_strategy instead
|
|
129
|
+
| Enable read from replicas in READONLY mode.
|
|
137
130
|
When set to true, read commands will be assigned between the primary and
|
|
138
131
|
its replications in a Round-Robin manner.
|
|
132
|
+
The data read from replicas is eventually consistent with the data in primary nodes.
|
|
133
|
+
:param load_balancing_strategy:
|
|
134
|
+
| Enable read from replicas in READONLY mode and defines the load balancing
|
|
135
|
+
strategy that will be used for cluster node selection.
|
|
136
|
+
The data read from replicas is eventually consistent with the data in primary nodes.
|
|
139
137
|
:param reinitialize_steps:
|
|
140
138
|
| Specifies the number of MOVED errors that need to occur before reinitializing
|
|
141
139
|
the whole cluster topology. If a MOVED error occurs and the cluster does not
|
|
@@ -145,19 +143,23 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
145
143
|
To avoid reinitializing the cluster on moved errors, set reinitialize_steps to
|
|
146
144
|
0.
|
|
147
145
|
:param cluster_error_retry_attempts:
|
|
148
|
-
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
| @deprecated - Please configure the 'retry' object instead
|
|
147
|
+
In case 'retry' object is set - this argument is ignored!
|
|
148
|
+
|
|
149
|
+
Number of times to retry before raising an error when :class:`~.TimeoutError`,
|
|
150
|
+
:class:`~.ConnectionError`, :class:`~.SlotNotCoveredError`
|
|
151
|
+
or :class:`~.ClusterDownError` are encountered
|
|
152
|
+
:param retry:
|
|
153
|
+
| A retry object that defines the retry strategy and the number of
|
|
154
|
+
retries for the cluster client.
|
|
155
|
+
In current implementation for the cluster client (starting form redis-py version 6.0.0)
|
|
156
|
+
the retry object is not yet fully utilized, instead it is used just to determine
|
|
157
|
+
the number of retries for the cluster client.
|
|
158
|
+
In the future releases the retry object will be used to handle the cluster client retries!
|
|
156
159
|
:param max_connections:
|
|
157
160
|
| Maximum number of connections per node. If there are no free connections & the
|
|
158
161
|
maximum number of connections are already created, a
|
|
159
|
-
:class:`~.MaxConnectionsError` is raised.
|
|
160
|
-
by :attr:`connection_error_retry_attempts`
|
|
162
|
+
:class:`~.MaxConnectionsError` is raised.
|
|
161
163
|
:param address_remap:
|
|
162
164
|
| An optional callable which, when provided with an internal network
|
|
163
165
|
address of a node, e.g. a `(host, port)` tuple, will return the address
|
|
@@ -213,10 +215,9 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
213
215
|
__slots__ = (
|
|
214
216
|
"_initialize",
|
|
215
217
|
"_lock",
|
|
216
|
-
"
|
|
218
|
+
"retry",
|
|
217
219
|
"command_flags",
|
|
218
220
|
"commands_parser",
|
|
219
|
-
"connection_error_retry_attempts",
|
|
220
221
|
"connection_kwargs",
|
|
221
222
|
"encoder",
|
|
222
223
|
"node_flags",
|
|
@@ -228,6 +229,18 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
228
229
|
"result_callbacks",
|
|
229
230
|
)
|
|
230
231
|
|
|
232
|
+
@deprecated_args(
|
|
233
|
+
args_to_warn=["read_from_replicas"],
|
|
234
|
+
reason="Please configure the 'load_balancing_strategy' instead",
|
|
235
|
+
version="5.3.0",
|
|
236
|
+
)
|
|
237
|
+
@deprecated_args(
|
|
238
|
+
args_to_warn=[
|
|
239
|
+
"cluster_error_retry_attempts",
|
|
240
|
+
],
|
|
241
|
+
reason="Please configure the 'retry' object instead",
|
|
242
|
+
version="6.0.0",
|
|
243
|
+
)
|
|
231
244
|
def __init__(
|
|
232
245
|
self,
|
|
233
246
|
host: Optional[str] = None,
|
|
@@ -236,10 +249,12 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
236
249
|
startup_nodes: Optional[List["ClusterNode"]] = None,
|
|
237
250
|
require_full_coverage: bool = True,
|
|
238
251
|
read_from_replicas: bool = False,
|
|
252
|
+
load_balancing_strategy: Optional[LoadBalancingStrategy] = None,
|
|
239
253
|
reinitialize_steps: int = 5,
|
|
240
254
|
cluster_error_retry_attempts: int = 3,
|
|
241
|
-
connection_error_retry_attempts: int = 3,
|
|
242
255
|
max_connections: int = 2**31,
|
|
256
|
+
retry: Optional["Retry"] = None,
|
|
257
|
+
retry_on_error: Optional[List[Type[Exception]]] = None,
|
|
243
258
|
# Client related kwargs
|
|
244
259
|
db: Union[str, int] = 0,
|
|
245
260
|
path: Optional[str] = None,
|
|
@@ -259,17 +274,15 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
259
274
|
socket_keepalive: bool = False,
|
|
260
275
|
socket_keepalive_options: Optional[Mapping[int, Union[int, bytes]]] = None,
|
|
261
276
|
socket_timeout: Optional[float] = None,
|
|
262
|
-
retry: Optional["Retry"] = None,
|
|
263
|
-
retry_on_error: Optional[List[Type[Exception]]] = None,
|
|
264
277
|
# SSL related kwargs
|
|
265
278
|
ssl: bool = False,
|
|
266
279
|
ssl_ca_certs: Optional[str] = None,
|
|
267
280
|
ssl_ca_data: Optional[str] = None,
|
|
268
|
-
ssl_cert_reqs: str = "required",
|
|
281
|
+
ssl_cert_reqs: Union[str, VerifyMode] = "required",
|
|
269
282
|
ssl_certfile: Optional[str] = None,
|
|
270
|
-
ssl_check_hostname: bool =
|
|
283
|
+
ssl_check_hostname: bool = True,
|
|
271
284
|
ssl_keyfile: Optional[str] = None,
|
|
272
|
-
ssl_min_version: Optional[
|
|
285
|
+
ssl_min_version: Optional[TLSVersion] = None,
|
|
273
286
|
ssl_ciphers: Optional[str] = None,
|
|
274
287
|
protocol: Optional[int] = 2,
|
|
275
288
|
address_remap: Optional[Callable[[Tuple[str, int]], Tuple[str, int]]] = None,
|
|
@@ -297,7 +310,6 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
297
310
|
kwargs: Dict[str, Any] = {
|
|
298
311
|
"max_connections": max_connections,
|
|
299
312
|
"connection_class": Connection,
|
|
300
|
-
"parser_class": ClusterParser,
|
|
301
313
|
# Client related kwargs
|
|
302
314
|
"credential_provider": credential_provider,
|
|
303
315
|
"username": username,
|
|
@@ -315,7 +327,6 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
315
327
|
"socket_keepalive": socket_keepalive,
|
|
316
328
|
"socket_keepalive_options": socket_keepalive_options,
|
|
317
329
|
"socket_timeout": socket_timeout,
|
|
318
|
-
"retry": retry,
|
|
319
330
|
"protocol": protocol,
|
|
320
331
|
}
|
|
321
332
|
|
|
@@ -335,21 +346,19 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
335
346
|
}
|
|
336
347
|
)
|
|
337
348
|
|
|
338
|
-
if read_from_replicas:
|
|
349
|
+
if read_from_replicas or load_balancing_strategy:
|
|
339
350
|
# Call our on_connect function to configure READONLY mode
|
|
340
351
|
kwargs["redis_connect_func"] = self.on_connect
|
|
341
352
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
self.retry =
|
|
346
|
-
|
|
353
|
+
if retry:
|
|
354
|
+
self.retry = retry
|
|
355
|
+
else:
|
|
356
|
+
self.retry = Retry(
|
|
357
|
+
backoff=ExponentialWithJitterBackoff(base=1, cap=10),
|
|
358
|
+
retries=cluster_error_retry_attempts,
|
|
347
359
|
)
|
|
348
|
-
|
|
349
|
-
# Default errors for retrying
|
|
350
|
-
retry_on_error = [ConnectionError, TimeoutError]
|
|
360
|
+
if retry_on_error:
|
|
351
361
|
self.retry.update_supported_errors(retry_on_error)
|
|
352
|
-
kwargs.update({"retry": self.retry})
|
|
353
362
|
|
|
354
363
|
kwargs["response_callbacks"] = _RedisCallbacks.copy()
|
|
355
364
|
if kwargs.get("protocol") in ["3", 3]:
|
|
@@ -384,9 +393,8 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
384
393
|
)
|
|
385
394
|
self.encoder = Encoder(encoding, encoding_errors, decode_responses)
|
|
386
395
|
self.read_from_replicas = read_from_replicas
|
|
396
|
+
self.load_balancing_strategy = load_balancing_strategy
|
|
387
397
|
self.reinitialize_steps = reinitialize_steps
|
|
388
|
-
self.cluster_error_retry_attempts = cluster_error_retry_attempts
|
|
389
|
-
self.connection_error_retry_attempts = connection_error_retry_attempts
|
|
390
398
|
self.reinitialize_counter = 0
|
|
391
399
|
self.commands_parser = AsyncCommandsParser()
|
|
392
400
|
self.node_flags = self.__class__.NODE_FLAGS.copy()
|
|
@@ -557,15 +565,8 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
557
565
|
"""Get the kwargs passed to :class:`~redis.asyncio.connection.Connection`."""
|
|
558
566
|
return self.connection_kwargs
|
|
559
567
|
|
|
560
|
-
def
|
|
561
|
-
return self.retry
|
|
562
|
-
|
|
563
|
-
def set_retry(self, retry: "Retry") -> None:
|
|
568
|
+
def set_retry(self, retry: Retry) -> None:
|
|
564
569
|
self.retry = retry
|
|
565
|
-
for node in self.get_nodes():
|
|
566
|
-
node.connection_kwargs.update({"retry": retry})
|
|
567
|
-
for conn in node._connections:
|
|
568
|
-
conn.retry = retry
|
|
569
570
|
|
|
570
571
|
def set_response_callback(self, command: str, callback: ResponseCallbackT) -> None:
|
|
571
572
|
"""Set a custom response callback."""
|
|
@@ -602,6 +603,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
602
603
|
self.nodes_manager.get_node_from_slot(
|
|
603
604
|
await self._determine_slot(command, *args),
|
|
604
605
|
self.read_from_replicas and command in READ_COMMANDS,
|
|
606
|
+
self.load_balancing_strategy if command in READ_COMMANDS else None,
|
|
605
607
|
)
|
|
606
608
|
]
|
|
607
609
|
|
|
@@ -683,8 +685,8 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
683
685
|
"""
|
|
684
686
|
Execute a raw command on the appropriate cluster node or target_nodes.
|
|
685
687
|
|
|
686
|
-
It will retry the command as specified by
|
|
687
|
-
then raise an exception.
|
|
688
|
+
It will retry the command as specified by the retries property of
|
|
689
|
+
the :attr:`retry` & then raise an exception.
|
|
688
690
|
|
|
689
691
|
:param args:
|
|
690
692
|
| Raw command args
|
|
@@ -700,7 +702,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
700
702
|
command = args[0]
|
|
701
703
|
target_nodes = []
|
|
702
704
|
target_nodes_specified = False
|
|
703
|
-
retry_attempts = self.
|
|
705
|
+
retry_attempts = self.retry.get_retries()
|
|
704
706
|
|
|
705
707
|
passed_targets = kwargs.pop("target_nodes", None)
|
|
706
708
|
if passed_targets and not self._is_node_flag(passed_targets):
|
|
@@ -782,7 +784,11 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
782
784
|
# refresh the target node
|
|
783
785
|
slot = await self._determine_slot(*args)
|
|
784
786
|
target_node = self.nodes_manager.get_node_from_slot(
|
|
785
|
-
slot,
|
|
787
|
+
slot,
|
|
788
|
+
self.read_from_replicas and args[0] in READ_COMMANDS,
|
|
789
|
+
self.load_balancing_strategy
|
|
790
|
+
if args[0] in READ_COMMANDS
|
|
791
|
+
else None,
|
|
786
792
|
)
|
|
787
793
|
moved = False
|
|
788
794
|
|
|
@@ -799,10 +805,16 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
799
805
|
# and try again with the new setup
|
|
800
806
|
await self.aclose()
|
|
801
807
|
raise
|
|
802
|
-
except ClusterDownError:
|
|
808
|
+
except (ClusterDownError, SlotNotCoveredError):
|
|
803
809
|
# ClusterDownError can occur during a failover and to get
|
|
804
810
|
# self-healed, we will try to reinitialize the cluster layout
|
|
805
811
|
# and retry executing the command
|
|
812
|
+
|
|
813
|
+
# SlotNotCoveredError can occur when the cluster is not fully
|
|
814
|
+
# initialized or can be temporary issue.
|
|
815
|
+
# We will try to reinitialize the cluster topology
|
|
816
|
+
# and retry executing the command
|
|
817
|
+
|
|
806
818
|
await self.aclose()
|
|
807
819
|
await asyncio.sleep(0.25)
|
|
808
820
|
raise
|
|
@@ -862,6 +874,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
862
874
|
blocking_timeout: Optional[float] = None,
|
|
863
875
|
lock_class: Optional[Type[Lock]] = None,
|
|
864
876
|
thread_local: bool = True,
|
|
877
|
+
raise_on_release_error: bool = True,
|
|
865
878
|
) -> Lock:
|
|
866
879
|
"""
|
|
867
880
|
Return a new Lock object using key ``name`` that mimics
|
|
@@ -908,6 +921,11 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
908
921
|
thread-1 would see the token value as "xyz" and would be
|
|
909
922
|
able to successfully release the thread-2's lock.
|
|
910
923
|
|
|
924
|
+
``raise_on_release_error`` indicates whether to raise an exception when
|
|
925
|
+
the lock is no longer owned when exiting the context manager. By default,
|
|
926
|
+
this is True, meaning an exception will be raised. If False, the warning
|
|
927
|
+
will be logged and the exception will be suppressed.
|
|
928
|
+
|
|
911
929
|
In some use cases it's necessary to disable thread local storage. For
|
|
912
930
|
example, if you have code where one thread acquires a lock and passes
|
|
913
931
|
that lock instance to a worker thread to release later. If thread
|
|
@@ -925,6 +943,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
925
943
|
blocking=blocking,
|
|
926
944
|
blocking_timeout=blocking_timeout,
|
|
927
945
|
thread_local=thread_local,
|
|
946
|
+
raise_on_release_error=raise_on_release_error,
|
|
928
947
|
)
|
|
929
948
|
|
|
930
949
|
|
|
@@ -1026,7 +1045,23 @@ class ClusterNode:
|
|
|
1026
1045
|
return self._free.popleft()
|
|
1027
1046
|
except IndexError:
|
|
1028
1047
|
if len(self._connections) < self.max_connections:
|
|
1029
|
-
connection
|
|
1048
|
+
# We are configuring the connection pool not to retry
|
|
1049
|
+
# connections on lower level clients to avoid retrying
|
|
1050
|
+
# connections to nodes that are not reachable
|
|
1051
|
+
# and to avoid blocking the connection pool.
|
|
1052
|
+
# The only error that will have some handling in the lower
|
|
1053
|
+
# level clients is ConnectionError which will trigger disconnection
|
|
1054
|
+
# of the socket.
|
|
1055
|
+
# The retries will be handled on cluster client level
|
|
1056
|
+
# where we will have proper handling of the cluster topology
|
|
1057
|
+
retry = Retry(
|
|
1058
|
+
backoff=NoBackoff(),
|
|
1059
|
+
retries=0,
|
|
1060
|
+
supported_errors=(ConnectionError,),
|
|
1061
|
+
)
|
|
1062
|
+
connection_kwargs = self.connection_kwargs.copy()
|
|
1063
|
+
connection_kwargs["retry"] = retry
|
|
1064
|
+
connection = self.connection_class(**connection_kwargs)
|
|
1030
1065
|
self._connections.append(connection)
|
|
1031
1066
|
return connection
|
|
1032
1067
|
|
|
@@ -1177,9 +1212,7 @@ class NodesManager:
|
|
|
1177
1212
|
return self.nodes_cache.get(node_name)
|
|
1178
1213
|
else:
|
|
1179
1214
|
raise DataError(
|
|
1180
|
-
"get_node requires one of the following: "
|
|
1181
|
-
"1. node name "
|
|
1182
|
-
"2. host and port"
|
|
1215
|
+
"get_node requires one of the following: 1. node name 2. host and port"
|
|
1183
1216
|
)
|
|
1184
1217
|
|
|
1185
1218
|
def set_nodes(
|
|
@@ -1239,17 +1272,23 @@ class NodesManager:
|
|
|
1239
1272
|
self._moved_exception = None
|
|
1240
1273
|
|
|
1241
1274
|
def get_node_from_slot(
|
|
1242
|
-
self,
|
|
1275
|
+
self,
|
|
1276
|
+
slot: int,
|
|
1277
|
+
read_from_replicas: bool = False,
|
|
1278
|
+
load_balancing_strategy=None,
|
|
1243
1279
|
) -> "ClusterNode":
|
|
1244
1280
|
if self._moved_exception:
|
|
1245
1281
|
self._update_moved_slots()
|
|
1246
1282
|
|
|
1283
|
+
if read_from_replicas is True and load_balancing_strategy is None:
|
|
1284
|
+
load_balancing_strategy = LoadBalancingStrategy.ROUND_ROBIN
|
|
1285
|
+
|
|
1247
1286
|
try:
|
|
1248
|
-
if
|
|
1249
|
-
# get the server index
|
|
1287
|
+
if len(self.slots_cache[slot]) > 1 and load_balancing_strategy:
|
|
1288
|
+
# get the server index using the strategy defined in load_balancing_strategy
|
|
1250
1289
|
primary_name = self.slots_cache[slot][0].name
|
|
1251
1290
|
node_idx = self.read_load_balancer.get_server_index(
|
|
1252
|
-
primary_name, len(self.slots_cache[slot])
|
|
1291
|
+
primary_name, len(self.slots_cache[slot]), load_balancing_strategy
|
|
1253
1292
|
)
|
|
1254
1293
|
return self.slots_cache[slot][node_idx]
|
|
1255
1294
|
return self.slots_cache[slot][0]
|
|
@@ -1361,7 +1400,7 @@ class NodesManager:
|
|
|
1361
1400
|
if len(disagreements) > 5:
|
|
1362
1401
|
raise RedisClusterException(
|
|
1363
1402
|
f"startup_nodes could not agree on a valid "
|
|
1364
|
-
f
|
|
1403
|
+
f"slots cache: {', '.join(disagreements)}"
|
|
1365
1404
|
)
|
|
1366
1405
|
|
|
1367
1406
|
# Validate if all slots are covered or if we should try next startup node
|
|
@@ -1518,7 +1557,7 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
|
|
|
1518
1557
|
"""
|
|
1519
1558
|
Execute the pipeline.
|
|
1520
1559
|
|
|
1521
|
-
It will retry the commands as specified by :attr:`
|
|
1560
|
+
It will retry the commands as specified by retries specified in :attr:`retry`
|
|
1522
1561
|
& then raise an exception.
|
|
1523
1562
|
|
|
1524
1563
|
:param raise_on_error:
|
|
@@ -1534,29 +1573,28 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
|
|
|
1534
1573
|
return []
|
|
1535
1574
|
|
|
1536
1575
|
try:
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
await self._client.initialize()
|
|
1540
|
-
|
|
1576
|
+
retry_attempts = self._client.retry.get_retries()
|
|
1577
|
+
while True:
|
|
1541
1578
|
try:
|
|
1579
|
+
if self._client._initialize:
|
|
1580
|
+
await self._client.initialize()
|
|
1542
1581
|
return await self._execute(
|
|
1543
1582
|
self._client,
|
|
1544
1583
|
self._command_stack,
|
|
1545
1584
|
raise_on_error=raise_on_error,
|
|
1546
1585
|
allow_redirections=allow_redirections,
|
|
1547
1586
|
)
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1587
|
+
|
|
1588
|
+
except self.__class__.ERRORS_ALLOW_RETRY as e:
|
|
1589
|
+
if retry_attempts > 0:
|
|
1590
|
+
# Try again with the new cluster setup. All other errors
|
|
1591
|
+
# should be raised.
|
|
1592
|
+
retry_attempts -= 1
|
|
1552
1593
|
await self._client.aclose()
|
|
1553
1594
|
await asyncio.sleep(0.25)
|
|
1554
1595
|
else:
|
|
1555
1596
|
# All other errors should be raised.
|
|
1556
|
-
raise
|
|
1557
|
-
|
|
1558
|
-
# If it fails the configured number of times then raise an exception
|
|
1559
|
-
raise exception
|
|
1597
|
+
raise e
|
|
1560
1598
|
finally:
|
|
1561
1599
|
self._command_stack = []
|
|
1562
1600
|
|
|
@@ -1616,24 +1654,31 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
|
|
|
1616
1654
|
if isinstance(result, Exception):
|
|
1617
1655
|
command = " ".join(map(safe_str, cmd.args))
|
|
1618
1656
|
msg = (
|
|
1619
|
-
f"Command # {cmd.position + 1}
|
|
1620
|
-
f"
|
|
1657
|
+
f"Command # {cmd.position + 1} "
|
|
1658
|
+
f"({truncate_text(command)}) "
|
|
1659
|
+
f"of pipeline caused error: {result.args}"
|
|
1621
1660
|
)
|
|
1622
1661
|
result.args = (msg,) + result.args[1:]
|
|
1623
1662
|
raise result
|
|
1624
1663
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
#
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1664
|
+
default_cluster_node = client.get_default_node()
|
|
1665
|
+
|
|
1666
|
+
# Check whether the default node was used. In some cases,
|
|
1667
|
+
# 'client.get_default_node()' may return None. The check below
|
|
1668
|
+
# prevents a potential AttributeError.
|
|
1669
|
+
if default_cluster_node is not None:
|
|
1670
|
+
default_node = nodes.get(default_cluster_node.name)
|
|
1671
|
+
if default_node is not None:
|
|
1672
|
+
# This pipeline execution used the default node, check if we need
|
|
1673
|
+
# to replace it.
|
|
1674
|
+
# Note: when the error is raised we'll reset the default node in the
|
|
1675
|
+
# caller function.
|
|
1676
|
+
for cmd in default_node[1]:
|
|
1677
|
+
# Check if it has a command that failed with a relevant
|
|
1678
|
+
# exception
|
|
1679
|
+
if type(cmd.result) in self.__class__.ERRORS_ALLOW_RETRY:
|
|
1680
|
+
client.replace_default_node()
|
|
1681
|
+
break
|
|
1637
1682
|
|
|
1638
1683
|
return [cmd.result for cmd in stack]
|
|
1639
1684
|
|