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

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]

总结

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

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

时间: 2024-09-30 14:33:38

django 重写 mysql 连接库实现连接池的相关文章

Qt动态连接库/静态连接库创建与使用,QLibrary动态加载库

版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt动态连接库/静态连接库创建与使用,QLibrary动态加载库     本文地址:http://techieliang.com/2017/12/680/ 文章目录 1. 动态连接库创建与使用  1.1. 项目创建  1.2. 调用-使用.h文件 2. 静态库创建及使用  2.1. 创建  2.2. 使用 3. QLibrary动态加载动态库  3.1. 介绍  3.2. 范例  3.3.

mysql忘记root密码连接本地库

今天想做个小项目,决定用mysql数据库,但是好久没用mysql了,也忘掉了当时建库时root密码是什么了,找到了一篇文章,在这里记录下. Windows下mysql忘记root密码的解决方法: Mysql版本:5.1.55-community MySQL Community Server (GPL) 1. 首先检查mysql服务是否启动,若已启动则先将其停止服务,可在开始菜单的运行,使用命令: net stop mysql 或者在windows任务管理器中结束mysqld.exe进程,或者在控

MySQL与php的连接操作要点

MySQL扩展库操作MySQL数据库的步骤如下: 1:获取连接. 2:选取书库. 3:设置操作编码. 4:发送SQL指令(MySQL数据库可以分为四种指令: 1:ddl: 数据定义语言. 2:dml:数据操语言(如CURD): 3:dql:数据查询语言.(如select) 4:dtl:数据事物语言. ). 5:接收返回结果,并且处理. 6:断开连接. 具体的示例代码如下: <pre name="code" class="php"><pre name

mysql连接的空闲时间超过8小时后 MySQL自动断开该连接解决方案 详细出处参考:http://www.jb51.net/article/32284.htm

MySQL 的默认设置下,当一个连接的空闲时间超过8小时后,MySQL 就会断开该连接,而 c3p0 连接池则以为该被断开的连接依然有效.在这种情况下,如果客户端代码向 c3p0 连接池请求连接的话,连接池就会把已经失效的连接返回给客户端,客户端在使用该失效连接的时候即抛出异常 解决这个问题的办法有三种: 1. 增加 MySQL 的 wait_timeout 属性的值. 修改 /etc/mysql/my.cnf文件,在 [mysqld] 节中设置: # Set a connection to w

mysql长连接与短连接

什么是长连接? 其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态. 通常的短连接操作步骤是: 连接->数据传输->关闭连接: 而长连接通常就是: 连接->数据传输->保持连接->数据传输->保持连接->…………->关闭连接: 这就要求长连接在没有数据通信时,定时发送数据包,以维持连接状态,短连接在没有数据传输时直接关闭就行了 什么时候用长连接,短连接? 长连接主要用于在少数客户端与服务端的频繁通信,因为这时候如果用短连接频繁通信

PHP-数据库长连接mysql_pconnect的细节

PHP的MySQL持久化连接,美好的目标,却拥有糟糕的口碑,往往令人敬而远之.这到底是为啥么.近距离观察后发现,这家伙也不容易啊,要看Apache的脸色,还得听MySQL指挥. 对于作为Apache模块运行的PHP来说,要实现MySQL持久化连接,首先得取决于Apache这个web服务器是否支持Keep-Alive. Keep-Alive Keep-Alive是什么东西?它是http协议的一部分,让我们复习一下没有Keep-Alive的http请求,从客户在浏览器输入一个有 效url地址开始,浏

稳定高效的MySQL与MSSQL的连接复用/读写分离/Sharding-把HTTP的技术推进到数据层

读了一些有关数据库读写分离的文章,大多是官方开源的MySQL-Proxy以及其衍生项目,而末尾往往谈到在实际部署时的困难重重.首先学习测试和部署的周期较长,同时在数据库中间多加了一层代理便多了一层故障点,需要专业的运维人员来继续开发和耐心维护,如出现问题只能把问题交给社区.在业务压力增大后往往发现这层代理的延迟越来越高形成瓶颈. 因此我来谈谈NetScaler的DataStream技术.Citrix NetScaler负载均衡业内第一个也是目前唯一一个支持MySQL和MS SQL 等协议内容的产

Node.js连接Mysql,并把连接集成进Express中间件中

引言 在node.js连接mysql的过程,我们通常有两种连接方法,普通连接和连接池. 这两种方法较为常见,当我们使用express框架时还会选择使用中间express-myconnection,可以单独对mysql配置,也可以把connection集成到express中间件中. 最后送上一个node.js 连接各种主流数据库示例代码. 前提条件 1.安装mysql对应的驱动,npm install mysql 2.安装第三方插件express-connection, npm install e

mysql重连,连接丢失问题:The last packet sent successfully to the server was……

1.log错误日志: -org.hibernate.exception.JDBCConnectionException: The last packet successfully received from the server was 51,999,860 milliseconds ago. The last packet sent successfully to the server was 51,999,860 milliseconds ago. is longer than the se