码迷,mamicode.com
首页 > 数据库 > 详细

django 重写 mysql 连接库实现连接池

时间:2019-12-05 11:41:08      阅读:255      评论:0      收藏:0      [点我收藏+]

标签:attr   any   map   数据   覆盖   数据库操作   cli   param   默认   

django 重写 mysql 连接库实现连接池

问题

django 项目使用 gunicorn + gevent 部署,并设置 CONN_MAX_AGE 会导致 mysql 数据库连接数飙升,在高并发模式可能会出现 too many connections 错误。该怎么解决这个问题呢?首先看下 django 源码,找到问题的根源。

本文 django 版本为 2.2.3。

问题分析

首先查看连接部分源码:

# django/db/backends/mysql/base.py

class DatabaseWrapper(BaseDatabaseWrapper):
    vendor = 'mysql'
    ...
    ...
    ...
    def get_new_connection(self, conn_params):
        # 每次查询都会重新建立连接
        return Database.connect(**conn_params)
    ...
    ...
    ...

再查看其基类 BaseDatabaseWrapper

# django/db/backends/base/base.py

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:
                # 每次查询完又要调用 close 关闭连接
                return self.connection.close()
    ...
    ...
    ...

查看源码发现 django 连接 mysql 时没有使用连接池,导致每次数据库操作都要新建新的连接并查询完后关闭,更坑的是按照 django 的官方文档设置 CONN_MAX_AGE 参数是为了复用连接,然后设置了 CONN_MAX_AGE 后,每个新连接查询完后并不会 close 掉,而是一直在那占着。

问题解决

通过重写 django 官方 mysql 连接库实现连接池解决。

settings.py 配置


...
DATABASES = {
    'default': {
        'ENGINE': 'db_pool.mysql',     # 重写 mysql 连接库实现连接池
        'NAME': 'devops',
        'USER': 'devops',
        'PASSWORD': 'devops',
        'HOST': '192.168.223.111',
        'PORT': '3306',
        'CONN_MAX_AGE': 600,
        # 数据库连接池大小,mysql 总连接数大小为:连接池大小 * 服务进程数
        'DB_POOL_SIZE': 20,     # 默认 5 个
        'OPTIONS': {
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
         },
    }
}
...

目录结构

db_pool/
├── __init__.py
└── mysql
    ├── base.py
    └── __init__.py
  • db_pool 位于 django 项目根目录

base.py

# -*- coding: utf-8 -*-
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

DEFAULT_DB_POOL_SIZE = 5


class DatabaseWrapper(_DatabaseWrapper):
    def get_new_connection(self, conn_params):
        # 获取 DATABASES 配置字典中的 DB_POOL_SIZE 参数
        pool_size = self.settings_dict.get('DB_POOL_SIZE') or DEFAULT_DB_POOL_SIZE
        return ConnectPool.instance(conn_params, pool_size).get_connection()

    def _close(self):
        return None  # 覆盖掉原来的 close 方法,查询结束后连接不会自动关闭


class ConnectPool(object):
    def __init__(self, conn_params, pool_size):
        self.conn_params = conn_params
        self.pool_size = pool_size
        self.connects = []

    # 实现连接池的单例
    @staticmethod
    def instance(conn_params, pool_size):
        if not hasattr(ConnectPool, '_instance'):
            ConnectPool._instance = ConnectPool(conn_params, pool_size)
        return ConnectPool._instance

    def get_connection(self):
        if len(self.connects) < self.pool_size:
            new_connect = Database.connect(**self.conn_params)
            self.connects.append(new_connect)
            return new_connect
        index = random.randint(0, self.pool_size - 1)   # 随机返回连接池中的连接
        try:
            # 检测连接是否有效,去掉性能更好,但建议保留
            self.connects[index].ping()
        except Exception:
            self.connects[index] = Database.connect(**self.conn_params)
        return self.connects[index]

总结

利用连接池 + 假关闭的方式解决过高连接数的问题。

django 重写 mysql 连接库实现连接池

标签:attr   any   map   数据   覆盖   数据库操作   cli   param   默认   

原文地址:https://www.cnblogs.com/leffss/p/11988183.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!