Настройка redis-кластера с Django

Заявление о проблеме Используйте django-redis с кластеризацией Redis.

При взаимодействии с кэшем возникает ошибка. Ошибка указывает на операцию pickle над экземпляром класса ConnectionPool, где один из его атрибутов, блокировка потока, не может быть сериализован и приводит к следующей ошибке:

  TypeError: cannot pickle '_thread.lock' object

Чтобы воспроизвести Шаги для воспроизведения поведения:

  1. Запустите кластер redis, на который указывает REDIS_URL.
  2. Установите 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,
                },
            }
        }
    }
    
  3. Запустите консоль Django и попробуйте взаимодействовать с кэшем, например, cache.get("somekey")
  4. .

Ожидаемое поведение. Значение ключа из кластера 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()
Вернуться на верх