内容简介:前段时间分享了一篇文章也抛出了一个问题: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 使用多个数据库连接
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ajax开发精要
柯自聪 / 电子工业出版社 / 2006 / 45.00
书籍目录: 概念篇 第1章 Ajax介绍 2 1.1 Ajax的由来 2 1.2 Ajax的定义 3 1.3 Web应用程序的解决方案 5 1.4 Ajax的工作方式 7 1.5 小结 8 第2章 B/S请求响应机制与Web开发模式 9 2.1 HTTP请求响应模型 9 2.2 B/S架构的请求响应机......一起来看看 《Ajax开发精要》 这本书的介绍吧!