内容简介:前段时间分享了一篇文章也抛出了一个问题:gunicorn+gevent+django+CONN_MAX_AGE会导致数据库连接数飙升,直至占满。如果一定要利用协程的方式启动,该怎么解决这个问题呢?看了一下django源码,找到了问题的根源,写了一下解决办法,下边分享一下。还是利用上一篇文章
原文连接
引言
前段时间分享了一篇 如何提高django的并发能力 文章,文章的最后结论是采用gunicorn+gthread+django的方式来提高并发能力,该方法简单的说是利用的多线程。
文章也抛出了一个问题:gunicorn+gevent+django+CONN_MAX_AGE会导致数据库连接数飙升,直至占满。如果一定要利用协程的方式启动,该怎么解决这个问题呢?看了一下django源码,找到了问题的根源,写了一下解决办法,下边分享一下。
说明
还是利用上一篇文章 如何提高django的并发能力 的数据模型, 这次以get一条数据为例 ,由于某些原因(好吧手里没有资源),采用了配置稍低的机器:
- 服务器: 4核+4G (docker)
- 压测机: 4核+2G (docker)
- django: 2.0.8
- msyql: 4核+4G(docker) max_connections:1000 max_user_connections:1000
压测方式及命令
- 压测方式:
压测方式
- 压测命令:
- ysab: ysab -n 800 -r 10 -u http://B_ip:8080/test'
- 备注: 欢迎使用ysab, <font color=#7B68EE>ysab文档</font>
重现问题
settings
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'ce', 'USER': 'root', 'PASSWORD': '', 'HOST': '192.168.96.95', 'PORT': '3306', 'CONN_MAX_AGE': 600, } }
启动及压测结果
-
启动: gunicorn --env DJANGO_SETTINGS_MODULE=test_dj21.settings test_dj21.wsgi:application -w 8 -b 0.0.0.0:8080 -k gevent --max-requests 40960 --max-requests-jitter 5120
-
数据库连接数展示
数据库连接数
-
qps展示
qps
为什么能达到1000多, 因为一直再查同一条数据。
问题分析与解决
数据库连接数为什么这么高
# django/db/backends/mysql/base.py class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'mysql' . . def get_new_connection(self, conn_params): c = Database.connect(**conn_params) print(id(c)) # 好吧我刻意打印了一下这个id, 每次查询都会重新建立连接用新连接操作 return c
还有一处诡异的代码
class BaseDatabaseWrapper: """Represent a database connection.""" # Mapping of Field objects to their column types. data_types = {} . . def _close(self): if self.connection is not None: with self.wrap_database_errors: print('foo close') # 每次查询完又要调用close return self.connection.close()
经过上边的代码,django关于 mysql 的部分没有使用连接池,导致每次数据库操作都要新建新的连接。更让我有些蒙的是,按照django的文档CONN_MAX_AGE是为了复用连接,但是为什么每次都要新建连接呢?如此看来并没有复用连接。而且最难受的是一旦我们设置了CONN_MAX_AGE,连接并不会被close掉,而是一直在那占着。
也许是我使用的问题,出现了这个问题。不管如何,最后想了解决办法,请往下看
问题的解决
代码部分
- settings代码
DATABASES = { 'default': { 'ENGINE': 'test_dj21.db.backends.mysql', # 好吧核心都在这 'NAME': 'ce', 'USER': 'root', 'PASSWORD': '', 'HOST': '10.10.10.10', 'PORT': '3306', 'CONN_MAX_AGE': 600, } }
-
test_dj21.db.backends.mysql所在位置
tree
- base.py
import random from django.core.exceptions import ImproperlyConfigured try: import MySQLdb as Database except ImportError as err: raise ImproperlyConfigured( 'Error loading MySQLdb module.\n' 'Did you install mysqlclient?' ) from err from django.db.backends.mysql.base import * from django.db.backends.mysql.base import DatabaseWrapper as _DatabaseWrapper class DatabaseWrapper(_DatabaseWrapper): def get_new_connection(self, conn_params): return ConnectPool.instance(conn_params).get_connection() def _close(self): return None # 假关闭 class ConnectPool(object): def __init__(self, conn_params): self.conn_params = conn_params self.n = 5 self.connects = [] # 未实现单例,实现连接池 @staticmethod def instance(conn_params): if not hasattr(ConnectPool, '_instance'): ConnectPool._instance = ConnectPool(conn_params) return ConnectPool._instance def get_connection(self): c = None if len(self.connects) <= self.n: c = Database.connect(**self.conn_params) self.connects.append(c) if c: return c index = random.randint(0, self.n) try: self.connects[index].ping() except Exception as e: self.connects[index] = Database.connect(**self.conn_params) return self.connects[index]
压测结果
-
数据库连接数展示
数据库连接数
-
qps展示
qps
总结
利用连接池+假关闭的方式解决过高连接数的问题,如果有更好的建议,可以讨论。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- EF Core 小坑:DbContextPool 会引起数据库连接池连接耗尽
- 数据库连接池
- 数据库连接池设置
- 怎样获知数据库的连接属性?
- 怎样获知数据库的连接属性?
- Laravel 使用多个数据库连接
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Algorithmic Beauty of Plants
Przemyslaw Prusinkiewicz、Aristid Lindenmayer / Springer / 1996-4-18 / USD 99.00
Now available in an affordable softcover edition, this classic in Springer's acclaimed Virtual Laboratory series is the first comprehensive account of the computer simulation of plant development. 150......一起来看看 《The Algorithmic Beauty of Plants》 这本书的介绍吧!