Posted on 2017-07-02 | In python |[](https://yunsonbai.top/2017/07...python
採用mysql鏈接池,實現鏈接複用,解決gunicorn+gevent+django數據庫高鏈接數問題mysql
原文鏈接nginx
以前分享了一篇如何提升django的併發能力文章,文章的最後結論是採用gunicorn+gthread+django的方式來提升併發能力,該方法簡單的說是利用的多線程。
文章也拋出了一個問題:gunicorn+gevent+django+CONN_MAX_AGE會致使數據庫鏈接數飆升,直至佔滿。若是必定要利用協程的方式啓動,該怎麼解決這個問題呢?看了一下django源碼,找到了問題的根源,寫了一下解決辦法,下邊分享一下。golang
仍是利用上一篇文章如何提升django的併發能力的數據模型,此次以get一條數據爲例,因爲某些緣由(好吧手裏沒有資源),採用了配置稍低的機器:sql
壓測命令:docker
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'ce',
'USER': 'root',
'PASSWORD': '',
'HOST': '192.168.96.95',
'PORT': '3306',
'CONN_MAX_AGE': 600,
}
}數據庫
# django/db/backends/mysql/base.py
class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'mysql'
.
. django
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掉,而是一直在那佔着。
也許是我使用的問題,出現了這個問題。無論如何,最後想了解決辦法,請往下看
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]
利用鏈接池+假關閉的方式解決太高鏈接數的問題,若是有更好的建議,能夠討論。