通过CONN_MAX_AGE优化Django的数据库连接
上周对我们用Django+Django-rest-framework提供的一套接口进行了压力测试。压测的过程中,收到DBA通知——数据库连接数过多,希望我们优化下程序。具体症状就是,如果设置mysql的最大连接数为1000,压测过程中,很快连接数就会达到上限,调整上限到2000,依然如此。
Django的数据库连接
Django对数据库的链接处理是这样的,Django程序接受到请求之后,在第一访问数据库的时候会创建一个数据库连接,直到请求结束,关闭连接。下次请求也是如此。因此,这种情况下,随着访问的并发数越来越高,就会产生大量的数据库连接。也就是我们在压测时出现的情况。
关于Django每次接受到请求和处理完请求时对数据库连接的操作,最后会从源码上来看看。
使用CONN_MAX_AGE减少数据库请求
上面说了,每次请求都会创建新的数据库连接,这对于高访问量的应用来说完全是不可接受的。因此在Django1.6时,提供了持久的数据库连接,通过DATABASE配置上添加CONN_MAX_AGE来控制每个连接的最大存活时间。具体使用可以参考最后的链接。
这个参数的原理就是在每次创建完数据库连接之后,把连接放到一个Theard.local的实例中。在request请求开始结束的时候,打算关闭连接时会判断是否超过CONN_MAX_AGE设置这个有效期。这是关闭。每次进行数据库请求的时候其实只是判断local中有没有已存在的连接,有则复用。
基于上述原因,Django中对于CONN_MAX_AGE的使用是有些限制的,使用不当,会事得其反。因为保存的连接是基于线程局部变量的,因此如果你部署方式采用多线程,必须要注意保证你的最大线程数不会多余数据库能支持的最大连接数。另外,如果使用开发模式运行程序(直接runserver的方式),建议不要设置CONN_MAX_AGE,因为这种情况下,每次请求都会创建一个Thread。同时如果你设置了CONN_MAX_AGE,将会导致你创建大量的不可复用的持久的连接。
CONN_MAX_AGE设置多久
CONN_MAX_AGE的时间怎么设置主要取决于数据库对空闲连接的管理,比如你的MySQL设置了空闲1分钟就关闭连接,那你的CONN_MAX_AGE就不能大于一分钟,不过DBA已经习惯了程序中的线程池的概念,会在数据库中设置一个较大的值。
优化结果
了解了上述过程之后,配置了CONN_MAX_AGE参数,再次测试,终于没有接到DBA通知,查看数据库连接数,最大700多。
最好的文档是代码
Django的文档上只是简单得介绍了原理和使用方式,对于好奇的同学来说,这个显然是不够的。于是我也好奇的看了下代码,把相关的片段贴到这里。
首先是一次请求开始和结束时对连接的处理
#### 请求开始# django.core.handlers.wsgi.pyclass WSGIHandler(base.BaseHandler):initLock = Lock()request_class = WSGIRequestdef __call__(self, environ, start_response):# ..... 省略若干代码# 触发request_started这个Signalsignals.request_started.send(sender=self.__class__, environ=environ)try:request = self.request_class(environ)except UnicodeDecodeError:logger.warning('Bad Request (UnicodeDecodeError)',exc_info=sys.exc_info(),extra={'status_code': 400,})# 请求结束class HttpResponseBase(six.Iterator):"""An HTTP response base class with dictionary-accessed headers.This class doesn't handle content. It should not be used directly.Use the HttpResponse and StreamingHttpResponse subclasses instead."""def close(self):for closable in self._closable_objects:try:closable.close()except Exception:pass# 请求结束时触发request_finished这个触发器signals.request_finished.send(sender=self._handler_class)
这里只是触发,那么在哪对这些signal进行处理呢?
# django.db.__init__.pyfrom django.db.utils import ConnectionHandlerconnections = ConnectionHandler()# Register an event to reset saved queries when a Django request is started.def reset_queries(**kwargs):for conn in connections.all():conn.queries_log.clear()signals.request_started.connect(reset_queries)# Register an event to reset transaction state and close connections past# their lifetime.def close_old_connections(**kwargs):for conn in connections.all():conn.close_if_unusable_or_obsolete()signals.request_started.connect(close_old_connections)signals.request_finished.connect(close_old_connections)
在这里对触发的signal进行了处理,从代码上看,逻辑就是,遍历所有已存在的链接,关闭不可用的连接。
再来看ConnectionHandler代码:
class ConnectionHandler(object):def __init__(self, databases=None):"""databases is an optional dictionary of database definitions (structuredlike settings.DATABASES)."""# databases来自settings对数据库的配置self._databases = databasesself._connections = local()@cached_propertydef databases(self):if self._databases is None:self._databases = settings.DATABASESif self._databases == {}:self._databases = {DEFAULT_DB_ALIAS: {'ENGINE': 'django.db.backends.dummy',},}if DEFAULT_DB_ALIAS not in self._databases:raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)return self._databasesdef __iter__(self):return iter(self.databases)def all(self):# 调用__iter__和__getitem__return [self[alias] for alias in self]def __getitem__(self, alias):if hasattr(self._connections, alias):return getattr(self._connections, alias)self.ensure_defaults(alias)self.prepare_test_settings(alias)db = self.databases[alias]backend = load_backend(db['ENGINE'])# 关键在这了,这个就是connconn = backend.DatabaseWrapper(db, alias)# 放到 local里setattr(self._connections, alias, conn)return conn
这个代码的关键就是生成对于backend的conn,并且放到local中。backend.DatabaseWrapper继承了db.backends.init.BaseDatabaseWrapper类的 close_if_unusable_or_obsolete() 的方法,来直接看下这个方法。
class BaseDatabaseWrapper(object):"""Represents a database connection."""def connect(self):"""Connects to the database. Assumes that the connection is closed."""# 连接数据库时读取配置中的CONN_MAX_AGEmax_age = self.settings_dict['CONN_MAX_AGE']self.close_at = None if max_age is None else time.time() + max_agedef close_if_unusable_or_obsolete(self):"""Closes the current connection if unrecoverable errors have occurred,or if it outlived its maximum age."""if self.connection is not None:# If the application didn't restore the original autocommit setting,# don't take chances, drop the connection.if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']:self.close()return# If an exception other than DataError or IntegrityError occurred# since the last commit / rollback, check if the connection works.if self.errors_occurred:if self.is_usable():self.errors_occurred = Falseelse:self.close()returnif self.close_at is not None and time.time() >= self.close_at:self.close()return
参考
https://docs.djangoproject.com/en/1.6/ref/databases/#persistent-database-connections https://github.com/django/django/blob/master/django/core/handlers/wsgi.py#L164 https://github.com/django/django/blob/master/django/http/response.py#L310 https://github.com/django/django/blob/master/django/db/init.py#L62 https://github.com/django/django/blob/master/django/db/utils.py#L252 https://github.com/django/django/blob/master/django/db/backends/init.py#L383
通过CONN_MAX_AGE优化Django的数据库连接的更多相关文章
- mybatis性能优化之降低数据库连接
做性能优化的最重要的功能就是降低数据库的交互.非常多程序猿一般在开发的时候仅仅考虑简单的实现功能,无论业务简单复杂,仅仅要实现即可. mybatis有个重要的功能就是考虑在联合查询时技巧: <? ...
- Django MySQL 数据库连接
Django 1.11 官方文档 常规说明 数据库连接 CONN_MAX_AGE 定义数据库连接时限(ALL) default:0 保存在每个请求结束时关闭数据库连接的历史行为. None:保持长连接 ...
- 优化Django ORM中的性能问题(含prefetch_related 和 select_related)
Django是个好工具,使用的很广泛. 在应用比较小的时候,会觉得它很快,但是随着应用复杂和壮大,就显得没那么高效了.当你了解所用的Web框架一些内部机制之后,才能写成比较高效的代码. 怎么查问题 W ...
- django修改数据库连接
settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'domain', 'USER ...
- Django之数据库连接与建模
Django数据库链接(这里以Mysql为例) 需要准备 Django1.10 pip install django==1.10 -i https://pypi.tuna.tsinghua.edu.c ...
- mybatis中的一点优化问题(数据库连接分开,别名,日志打印)
一:数据的链接 1.目录 2.新建一个db.properties driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3308/mybati ...
- django 数据库连接模块解析及简单长连接改造
django 数据库连接模块解析及简单长连接改造工作中纯服务端的项目用到了线程池和django的ORM部分.django 的数据库连接在每一个线程中开启一份,并在查询完毕后自动关闭连接. 线程池处理任 ...
- 浅谈提高Django性能
Django性能优化是一件困难的事情,但是也不常常如此: 下面4步将能够轻松的提高你的网站的性能,它们非常简单你应该将它们 作为标配. 持久化数据库连接 django1.6以后已经内置了数据库持久化连 ...
- django核心配置项
Django的默认配置文件中,包含上百条配置项目,其中很多是我们‘一辈子’都不碰到或者不需要单独配置的,这些项目在需要的时候再去查手册. 强调:配置的默认值不是在settings.py文件中!不要以为 ...
随机推荐
- noi.ac #289. 电梯(单调队列)
题意 题目链接 Sol 傻叉的我以为给出的\(t\)是单调递增的,然后\(100\rightarrow0\) 首先可以按\(t\)排序,那么转移方程为 \(f[i] = min_{j=0}^{i-1} ...
- 【Dojo 1.x】笔记6 配置对象dojoConfig的用处和真身所在
dojoConfig是对整个Dojo开发环境的配置,它的位置必须是页面的最前,可以是script标签也可以是独立的js文件. 它允许开发者为Dojo SDK的各个方面设置选项和默认行为. 但是,这个对 ...
- DataSnap 多层返回数据集分析FireDAC JSON
采用服务器返回数据,一种是返回字符串数据例如JSON,跨平台跨语言,任何语言调用都支持兼容,类似WEBService. 第二种是紧密结合c++builder语言,传输DataSet,可以是Client ...
- Xamarin 开发过的那些项目
您可能已经看到类似的统计数据:智能手机用户在手机媒体上花费了89%的时间使用应用程序.或者听说Gartner预测到2017年移动应用程序下载将产生价值770亿美元的收入.很难不考虑这些数字.今天,每个 ...
- leaflet动态路径
在leaflet中使用动态路径需要结合插件使用,对比了好几个插件,最终找到leaflet.motion比较合适: leaflet地址:https://leafletjs.com/ leaflet.mo ...
- 【原】Java学习笔记022 - 字符串
package cn.temptation; public class Sample01 { public static void main(String[] args) { // 字符串 // 定义 ...
- 如何设置Oracle数据库客户端字符集以及系统中的NLS_LANG环境变量
概述: 本地化是系统或软件运行的语言和文化环境.设置NLS_LANG环境参数是规定Oracle数据库软件本地化行为最简单的方式. NLS_LANG参数不但指定了客户端应用程序和Oracle数据库所使用 ...
- Bootstrap -- 下拉菜单、输入框组、导航菜单
Bootstrap -- 下拉菜单.输入框组.导航菜单 1. 下拉菜单 可以使用带有各种大小按钮的下拉菜单:.btn-lg..btn-sm 或 .btn-xs. 实现下拉菜单: <!DOCTYP ...
- SQLServer之删除触发器
删除触发器 注意事项 可以通过删除DML触发器或删除触发器表来删除DML触发器. 删除表时,将同时删除与表关联的所有触发器. 删除触发器时,会从 sys.objects.sys.triggers 和 ...
- Linux Collection:网络配置
PAS 缺少ifconfig 安装相应软件[不推荐],尽量使用 ip 命令 sudo apt install gnome-nettool 补充,显示IP地址: ip show address PAS ...