Setup redis-cluster with Django



问题说明使用django-redis与redis集群。

与缓存交互时发生错误。错误指向ConnectionPool类实例上的pickle操作,其中它的一个属性,线程锁,不能被序列化,并导致以下错误:

TypeError: cannot pickle '_thread.lock' object

繁殖重现行为的步骤:

  1. 运行REDIS_URL指向的redis集群
  2. 在Django设置文件中设置CACHES
    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")

预期行为从Redis集群获取的key值。

堆栈跟踪

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 Version: 3.2.13
  • Redis版本:7.0.0
  • redis-py Version: 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。锁,deepcopy()失败

解决方案# 1:[6]直接调用RedisCluster.from_urldjango_redis.client.DefaultClient.connect(),绕过ConnectionFactory和ConnectionPool。ConnectionPool.get_connection()生成致命的connection_pool。

解决方案# 2:创建RedisCluster的shim类,删除connection_pool;从kwargs [7] .

解决方案# 3:在使用RedisCluster之前先弄乱它[8]。redis.cluster.RedisCluster。__init __在通过之前擦除变量

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参见kulultaykalkan msg 2022-10-28Django-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()

最新更新