django併發優化

django數據庫鏈接複用

Posted on 2017-07-02 | In python |[](https://yunsonbai.top/2017/07...python

採用mysql鏈接池,實現鏈接複用,解決gunicorn+gevent+django數據庫高鏈接數問題mysql

原文鏈接nginx

[](https://yunsonbai.top/2017/07... "引言")引言

以前分享了一篇如何提升django的併發能力文章,文章的最後結論是採用gunicorn+gthread+django的方式來提升併發能力,該方法簡單的說是利用的多線程。
文章也拋出了一個問題:gunicorn+gevent+django+CONN_MAX_AGE會致使數據庫鏈接數飆升,直至佔滿。若是必定要利用協程的方式啓動,該怎麼解決這個問題呢?看了一下django源碼,找到了問題的根源,寫了一下解決辦法,下邊分享一下。golang

[](https://yunsonbai.top/2017/07... "說明")說明

仍是利用上一篇文章如何提升django的併發能力的數據模型,此次以get一條數據爲例,因爲某些緣由(好吧手裏沒有資源),採用了配置稍低的機器:sql

  • 服務器: 4核+4G (docker)
  • 壓測機: 4核+2G (docker)
  • django: 1.8.2
  • msyql: 4核+4G(docker) max_connections:1000 max_user_connections:1000

[](https://yunsonbai.top/2017/07... "壓測方式及命令")壓測方式及命令

[](https://yunsonbai.top/2017/07... "重現問題")重現問題

[](https://yunsonbai.top/2017/07... "settings")settings

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'ce',
'USER': 'root',
'PASSWORD': '',
'HOST': '192.168.96.95',
'PORT': '3306',
'CONN_MAX_AGE': 600,
}
}數據庫

[](https://yunsonbai.top/2017/07... "啓動及壓測結果")啓動及壓測結果

  • 啓動: 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多, 由於一直再查同一條數據。

[](https://yunsonbai.top/2017/07... "問題分析與解決")問題分析與解決

[](https://yunsonbai.top/2017/07... "數據庫鏈接數爲何這麼高")數據庫鏈接數爲何這麼高

# 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掉,而是一直在那佔着。
也許是我使用的問題,出現了這個問題。無論如何,最後想了解決辦法,請往下看

[](https://yunsonbai.top/2017/07... "問題的解決")問題的解決

[](https://yunsonbai.top/2017/07... "代碼部分")代碼部分

  • settings代碼
  • DATABASES = {
    'default': {
    'ENGINE': 'test_dj21.db.backends.mysql', # 好吧核心都在這
    'NAME': 'ce',
    'USER': 'root',
    'PASSWORD': '',
    'HOST': '192.168.96.95',
    '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]

[](https://yunsonbai.top/2017/07... "壓測結果")壓測結果

  • 數據庫鏈接數展現
    數據庫鏈接數
  • qps展現
    qps
    若是沒有self.connects[index].ping()操做壓測性能會更好,可是不建議去掉,須要檢查鏈接是否可用。

[](https://yunsonbai.top/2017/07... "總結")總結

利用鏈接池+假關閉的方式解決太高鏈接數的問題,若是有更好的建議,能夠討論。

[](https://yunsonbai.top/2017/07... "文章推薦")文章推薦

ysab基於golang的壓測工具
yunorm輕量接orm
nginx+tornado折騰筆記

相關文章
相關標籤/搜索