一、问题现象
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