gunicorn+gevent+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

压测方式及命令

重现问题

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': '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]

压测结果

  • 数据库连接数展示


    数据库连接数
  • qps展示


    qps

总结

利用连接池+假关闭的方式解决过高连接数的问题,如果有更好的建议,可以讨论。

文章推荐

ysab基于golang的压测工具
yunorm轻量接orm

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容