Настройка redis-кластера с Django
Заявление о проблеме
Используйте django-redis с кластеризацией Redis.
При взаимодействии с кэшем возникает ошибка.
Ошибка указывает на операцию pickle над экземпляром класса ConnectionPool, где
один из его атрибутов, блокировка потока, не может быть сериализован и приводит к следующей ошибке:
TypeError: cannot pickle '_thread.lock' object
Чтобы воспроизвести Шаги для воспроизведения поведения:
- Запустите кластер redis, на который указывает
REDIS_URL. - Установите
CACHESв файле настроек Django.CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": REDIS_URL, "OPTIONS": { "REDIS_CLIENT_CLASS": "redis.cluster.RedisCluster", "REDIS_CLIENT_KWARGS": { "url": REDIS_URL, }, } } } - Запустите консоль Django и попробуйте взаимодействовать с кэшем, например,
cache.get("somekey").
Ожидаемое поведение. Значение ключа из кластера Redis.
Трассировка стека
Traceback (most recent call last):
File "python3.9/site-packages/redis/cluster.py", line 1454, in initialize
copy_kwargs = copy.deepcopy(kwargs)
File "python3.9/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "python3.9/copy.py", line 230, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "python3.9/copy.py", line 172, in deepcopy
y = _reconstruct(x, memo, *rv)
File "python3.9/copy.py", line 270, in _reconstruct
state = deepcopy(state, memo)
File "python3.9/copy.py", line 146, in deepcopy
y = copier(x, memo)
File "python3.9/copy.py", line 230, in _deepcopy_dict
y[deepcopy(key, memo)] = deepcopy(value, memo)
File "python3.9/copy.py", line 161, in deepcopy
rv = reductor(4)
TypeError: cannot pickle '_thread.lock' object
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "python3.9/site-packages/IPython/core/interactiveshell.py", line 3552, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-6-48456cec0da8>", line 1, in <cell line: 1>
from django.core.cache import cache; cache.get("bingo")
File "python3.9/site-packages/django_redis/cache.py", line 91, in get
value = self._get(key, default, version, client)
File "python3.9/site-packages/django_redis/cache.py", line 31, in _decorator
return method(self, *args, **kwargs)
File "python3.9/site-packages/django_redis/cache.py", line 98, in _get
return self.client.get(key, default=default, version=version, client=client)
File "python3.9/site-packages/django_redis/client/default.py", line 253, in get
client = self.get_client(write=False)
File "python3.9/site-packages/django_redis/client/default.py", line 105, in get_client
self._clients[index] = self.connect(index)
File "python3.9/site-packages/django_redis/client/default.py", line 118, in connect
return self.connection_factory.connect(self._server[index])
File "python3.9/site-packages/django_redis/pool.py", line 72, in connect
connection = self.get_connection(params)
File "python3.9/site-packages/django_redis/pool.py", line 92, in get_connection
return self.redis_client_cls(
File "python3.9/site-packages/redis/cluster.py", line 592, in __init__
self.nodes_manager = NodesManager(
File "python3.9/site-packages/redis/cluster.py", line 1286, in __init__
self.initialize()
File "python3.9/site-packages/redis/cluster.py", line 1490, in initialize
raise RedisClusterException(
redis.exceptions.RedisClusterException: ERROR sending "cluster slots" command to redis server 127.0.0.1:7000. error: cannot pickle '_thread.lock' object
Среда:
- Версия Python: 3.9
- Django Redis Версия: 5.2.0
- Django Версия: 3.2.13
- Redis Версия: 7.0.0
- redis-py Версия: 4.3.1
Проблема в том, что django_redis.pool.ConnectionFactory.get_connection создает пул (класса redis.connection.ConnectionPool)
pool = self.get_or_create_connection_pool(params)
return self.redis_client_cls(
connection_pool=pool, **self.redis_client_cls_kwargs)
и передает его в
redis.cluster.RedisCluster.__init__( ... **kwargs ) (so kwargs now has connection_pool)
redis.cluster.NodesManager.__init__
redis.cluster.NodesManager.initialize()
kwargs = self.connection_kwargs
copy_kwargs = copy.deepcopy(kwargs) *******
*** die! ConnectionPool has a _thread.lock, not pickle-able. ***
Поскольку redis.connection.ConnectionPool имеет _thread.lock, deepcopy() не работает.
Решение #1: [6] вызывает RedisCluster.from_url непосредственно из django_redis.client.DefaultClient.connect(), минуя ConnectionFactory и ConnectionPool. ConnectionPool.get_connection() делает смертельный connection_pool.
Решение #2: Сделайте shim-класс RedisCluster, чтобы исключить "connection_pool" из kwargs [7].
Решение №3: Поработайте с RedisCluster, прежде чем использовать его [8]. redis.cluster.RedisCluster.__ init __ очищает kwargs перед передачей его в NodesManager.
kwargs = cleanup_kwargs(**kwargs)
Удаляет ключи, перечисленные в redis.cluster.KWARGS_DISABLED_KEYS, который имеет
KWARGS_DISABLED_KEYS = ("host", "port")
[8] добавляет "connection_pool", поэтому cleanup_kwargs() удаляет надоедливый ключ.
Ссылки
[6] https://github.com/jazzband/django-redis/issues/208
см. cKurultayKalkan msg 2022-10-28
django-redis + redis-py 4.0 (с кластером)
добавить класс shim в redis.cluster.RedisCluster
class CustomRedisCluster(DefaultClient):
def connect(self, index):
"""Override the connection retrival function."""
return RedisCluster.from_url(self._server[index])
в settings.py,
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_URL,
"OPTIONS": {
"CLIENT_CLASS": "redis_client.CustomRedisCluster",
},
}
}
[7] Добавьте класс адаптера перед RedisCluster, чтобы избавиться от надоедливого 'connection_pool'.
import redis.cluster
class RedisClusterShim( redis.cluster.RedisCluster ) :
def __init__( self, *args, **kwargs ) :
kwargs.pop( 'connection_pool', None )
# redis.cluster.NodesManager.initialize() does connection_pool.deepcopy,
# but redis.connection.ConnectionPool has a _thread.lock, not pickle-able.
super().__init__( *args, **kwargs )
[8] перед использованием RedisCluster, например, добавьте его в settings.py
def messWithRedisCluster() :
import redis
redis.cluster.KWARGS_DISABLED_KEYS = ("host", "port", "connection_pool" )
messWithRedisCluster()