首页 > 数据库 >问题定位记录1:Djano-redis库报错No connection available

问题定位记录1:Djano-redis库报错No connection available

时间:2022-10-28 17:02:07浏览次数:57  
标签:available __ No cache redis connection client 报错 get

一、问题现象

MANO(网络管理与编排软件)原子层创建资源后写redis缓存时报错No connection avaiable

堆栈打印:

Traceback (most recent call last):

  File "/home/oes/.local/lib/python3.8/site-packages/django_redis/cache.py", line 31, in _decorator

    return method(self, *args, **kwargs)

  File "/home/oes/.local/lib/python3.8/site-packages/django_redis/cache.py", line 98, in _get

    return self.client.get(key, default=default, version=version, client=client)

  File "/home/oes/.local/lib/python3.8/site-packages/django_redis/client/default.py", line 260, in get

    raise ConnectionInterrupted(connection=client) from e

django_redis.exceptions.ConnectionInterrupted: Redis ConnectionError: No connection available.

 During handling of the above exception, another exception occurred:

 Traceback (most recent call last):

*********(前面业务打印已略去)

File "/home/oes/.local/lib/python3.8/site-packages/django_redis/cache.py", line 38, in _decorator

    raise e.__cause__

  File "/home/oes/.local/lib/python3.8/site-packages/django_redis/client/default.py", line 258, in get

    value = client.get(key)

  File "/home/oes/.local/lib/python3.8/site-packages/redis/client.py", line 1606, in get

    return self.execute_command('GET', name)

  File "/home/oes/.local/lib/python3.8/site-packages/redis/client.py", line 898, in execute_command

    conn = self.connection or pool.get_connection(command_name, **options)

  File "/home/oes/.local/lib/python3.8/site-packages/redis/connection.py", line 1370, in get_connection

    raise ConnectionError("No connection available."

二、问题定位

一开始怀疑是redis连接数量过多,导致无法创建redis连接,但是实际上登录redis后发现redis客户端可以连上,且发现redis客户端连接数并没有超标。然后开始尝试通过代码层面分析原因

(1)登录python manage shell,尝试复现traceback报错

>>> from django.core.cache import cache

>>> type(cache)

<class 'django.utils.connection.ConnectionProxy'>

>>> dir(cache)

['__class__', '__contains__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_alias', '_connections']

cache对象实际是 cache = ConnectionProxy(caches, DEFAULT_CACHE_ALIAS)初始化得到的:

 

 

真实的操作对象是cache._connections['default'], 其类属性如下:

>>> cache._connections

<django.core.cache.CacheHandler object at 0x7f72d76cd160>

>>> cache._alias

'default'

>>> cache._connections['default']

<django_redis.cache.RedisCache object at 0x7f72d48c4c10>

>>> cache._connections['default'].__dict__

{'default_timeout': 300, '_max_entries': 300, '_cull_frequency': 3, 'key_prefix': '', 'version': 1, 'key_func': <function default_key_func at 0x7f72d76df670>, '_server': 'redis://127.0.0.1:26061', '_params': {'OPTIONS': {'CONNECTION_POOL_CLASS': 'redis.BlockingConnectionPool', 'CONNECTION_POOL_CLASS_KWARGS': {'max_connections': 400, 'timeout': 240}, 'DB': 6, 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'PASSWORD': '***'}}, '_client_cls': <class 'django_redis.client.default.DefaultClient'>, '_client': <django_redis.client.default.DefaultClient object at 0x7f72d48c4b50>, '_ignore_exceptions': False, '_log_ignored_exceptions': False, 'logger': None}

综上可以看出,django_redis.cache.RedisCache对象其实就是我们代码中实际使用的对象ctx, 其__dict__中的类属性是在setting中指定的:

CACHES = {
   'default': {
       'BACKEND': 'django_redis.cache.RedisCache',
       'LOCATION': "redis://%s:%s" % (url_ipv6_sup(config.REDIS_HOST), config.REDIS_PORT),
       'OPTIONS': {
           "CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool",
           "CONNECTION_POOL_CLASS_KWARGS": {"max_connections": 400, "timeout": 240},                                  
           "DB": config.REDIS_CACHE_DATABASE,                                                                                                
           "CLIENT_CLASS": "django_redis.client.DefaultClient",
           "PASSWORD": config.REDIS_PASSWD,
       },

   }

在执行ctx.get指令时,实际调用代码(将调用链用截图方式呈现):

 

 

 

 

 

 

 

 其中client对象是django_redis.client.DefaultClient,这个也是setting中指定的。我们实际调用的是django_redis.client.DefaultClient对象的get方法,

接下来继续追踪调用链路:

 

 

 

 

 

 

 在这里我们可以看到:client对象为setting中指定的,如果没有指定,默认使用 django_redis.pool.ConnectionFactory对象的connect方法和redis建立连接。

在初始化ConnectionFactory对象时,我们使用了setting中的 "CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool"指定连接类型

"CONNECTION_POOL_CLASS_KWARGS": {"max_connections": 400, "timeout": 240},指定连接池的参数,但是我们实际上配置的是CONNECTION_POOL_CLASS_KWARGS,所以项目中配置

的连接池参数没有生效。这个问题是项目从python2切到3的时候引入的,只影响并发能力,所以之前没有发现

 接下来进一步探究下当连接池参数未生效情况下,高并发业务为什么会导致报错:No connection avaiable

还是从调用链路开始入手:

 

 get_connection主要做了获取连接池和生成redis客户端对象的作用:

 

 这里的pool对象就是<class 'redis.connection.BlockingConnectionPool'>对象,由于我们的配置连接池参数没有生效,导致连接队列使用默认值50,连接超时时间为20s

redis client对象实际为redis.client.Redis对象,get方法实际上就是执行execute_command函数,但在执行前需要从连接池队列中获取连接,在获取连接的时候,采用协程阻塞的方法从队列中获取连接对象,连接队列是一个Lifo队列,在执行reset.put_nowait过程中队列里面放了50个None对象中,所以获取连接的时候如果获取的是None对象,就调用make_connection函数创建redis连接。然后使用连接执行get命令,使用结束后调用release方法重新将None对象放入连接队列中,如果连接队列过小,获取连接对象的时间超过timeout(20 s),self.pool.get就会抛出empty异常,外面捕获到empt异常后再往外抛No connection avaiable异常,所以并发如果大于50时容易产生此异常。

 

 

 

 

 

标签:available,__,No,cache,redis,connection,client,报错,get
From: https://www.cnblogs.com/kevin-zsq/p/16836641.html

相关文章