上周对我们用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的文档上只是简单得介绍了原理和使用方式,对于好奇的同学来说,这个显然是不够的。于是我也好奇的看了下代码,把相关的片段贴到这里。

首先是一次请求开始和结束时对连接的处理

  1. #### 请求开始
  2. # django.core.handlers.wsgi.py
  3. class WSGIHandler(base.BaseHandler):
  4. initLock = Lock()
  5. request_class = WSGIRequest
  6. def __call__(self, environ, start_response):
  7. # ..... 省略若干代码
  8. # 触发request_started这个Signal
  9. signals.request_started.send(sender=self.__class__, environ=environ)
  10. try:
  11. request = self.request_class(environ)
  12. except UnicodeDecodeError:
  13. logger.warning('Bad Request (UnicodeDecodeError)',
  14. exc_info=sys.exc_info(),
  15. extra={
  16. 'status_code': 400,
  17. }
  18. )
  19. # 请求结束
  20. class HttpResponseBase(six.Iterator):
  21. """
  22. An HTTP response base class with dictionary-accessed headers.
  23. This class doesn't handle content. It should not be used directly.
  24. Use the HttpResponse and StreamingHttpResponse subclasses instead.
  25. """
  26. def close(self):
  27. for closable in self._closable_objects:
  28. try:
  29. closable.close()
  30. except Exception:
  31. pass
  32. # 请求结束时触发request_finished这个触发器
  33. signals.request_finished.send(sender=self._handler_class)

这里只是触发,那么在哪对这些signal进行处理呢?

  1. # django.db.__init__.py
  2. from django.db.utils import ConnectionHandler
  3. connections = ConnectionHandler()
  4. # Register an event to reset saved queries when a Django request is started.
  5. def reset_queries(**kwargs):
  6. for conn in connections.all():
  7. conn.queries_log.clear()
  8. signals.request_started.connect(reset_queries)
  9. # Register an event to reset transaction state and close connections past
  10. # their lifetime.
  11. def close_old_connections(**kwargs):
  12. for conn in connections.all():
  13. conn.close_if_unusable_or_obsolete()
  14. signals.request_started.connect(close_old_connections)
  15. signals.request_finished.connect(close_old_connections)

在这里对触发的signal进行了处理,从代码上看,逻辑就是,遍历所有已存在的链接,关闭不可用的连接。

再来看ConnectionHandler代码:

  1. class ConnectionHandler(object):
  2. def __init__(self, databases=None):
  3. """
  4. databases is an optional dictionary of database definitions (structured
  5. like settings.DATABASES).
  6. """
  7. # databases来自settings对数据库的配置
  8. self._databases = databases
  9. self._connections = local()
  10. @cached_property
  11. def databases(self):
  12. if self._databases is None:
  13. self._databases = settings.DATABASES
  14. if self._databases == {}:
  15. self._databases = {
  16. DEFAULT_DB_ALIAS: {
  17. 'ENGINE': 'django.db.backends.dummy',
  18. },
  19. }
  20. if DEFAULT_DB_ALIAS not in self._databases:
  21. raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
  22. return self._databases
  23. def __iter__(self):
  24. return iter(self.databases)
  25. def all(self):
  26. # 调用__iter__和__getitem__
  27. return [self[alias] for alias in self]
  28. def __getitem__(self, alias):
  29. if hasattr(self._connections, alias):
  30. return getattr(self._connections, alias)
  31. self.ensure_defaults(alias)
  32. self.prepare_test_settings(alias)
  33. db = self.databases[alias]
  34. backend = load_backend(db['ENGINE'])
  35. # 关键在这了,这个就是conn
  36. conn = backend.DatabaseWrapper(db, alias)
  37. # 放到 local里
  38. setattr(self._connections, alias, conn)
  39. return conn

这个代码的关键就是生成对于backend的conn,并且放到local中。backend.DatabaseWrapper继承了db.backends.init.BaseDatabaseWrapper类的 close_if_unusable_or_obsolete() 的方法,来直接看下这个方法。

  1. class BaseDatabaseWrapper(object):
  2. """
  3. Represents a database connection.
  4. """
  5. def connect(self):
  6. """Connects to the database. Assumes that the connection is closed."""
  7. # 连接数据库时读取配置中的CONN_MAX_AGE
  8. max_age = self.settings_dict['CONN_MAX_AGE']
  9. self.close_at = None if max_age is None else time.time() + max_age
  10. def close_if_unusable_or_obsolete(self):
  11. """
  12. Closes the current connection if unrecoverable errors have occurred,
  13. or if it outlived its maximum age.
  14. """
  15. if self.connection is not None:
  16. # If the application didn't restore the original autocommit setting,
  17. # don't take chances, drop the connection.
  18. if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']:
  19. self.close()
  20. return
  21. # If an exception other than DataError or IntegrityError occurred
  22. # since the last commit / rollback, check if the connection works.
  23. if self.errors_occurred:
  24. if self.is_usable():
  25. self.errors_occurred = False
  26. else:
  27. self.close()
  28. return
  29. if self.close_at is not None and time.time() >= self.close_at:
  30. self.close()
  31. 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的数据库连接的更多相关文章

  1. mybatis性能优化之降低数据库连接

    做性能优化的最重要的功能就是降低数据库的交互.非常多程序猿一般在开发的时候仅仅考虑简单的实现功能,无论业务简单复杂,仅仅要实现即可. mybatis有个重要的功能就是考虑在联合查询时技巧: <? ...

  2. Django MySQL 数据库连接

    Django 1.11 官方文档 常规说明 数据库连接 CONN_MAX_AGE 定义数据库连接时限(ALL) default:0 保存在每个请求结束时关闭数据库连接的历史行为. None:保持长连接 ...

  3. 优化Django ORM中的性能问题(含prefetch_related 和 select_related)

    Django是个好工具,使用的很广泛. 在应用比较小的时候,会觉得它很快,但是随着应用复杂和壮大,就显得没那么高效了.当你了解所用的Web框架一些内部机制之后,才能写成比较高效的代码. 怎么查问题 W ...

  4. django修改数据库连接

    settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'domain', 'USER ...

  5. Django之数据库连接与建模

    Django数据库链接(这里以Mysql为例) 需要准备 Django1.10 pip install django==1.10 -i https://pypi.tuna.tsinghua.edu.c ...

  6. mybatis中的一点优化问题(数据库连接分开,别名,日志打印)

    一:数据的链接 1.目录 2.新建一个db.properties driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3308/mybati ...

  7. django 数据库连接模块解析及简单长连接改造

    django 数据库连接模块解析及简单长连接改造工作中纯服务端的项目用到了线程池和django的ORM部分.django 的数据库连接在每一个线程中开启一份,并在查询完毕后自动关闭连接. 线程池处理任 ...

  8. 浅谈提高Django性能

    Django性能优化是一件困难的事情,但是也不常常如此: 下面4步将能够轻松的提高你的网站的性能,它们非常简单你应该将它们 作为标配. 持久化数据库连接 django1.6以后已经内置了数据库持久化连 ...

  9. django核心配置项

    Django的默认配置文件中,包含上百条配置项目,其中很多是我们‘一辈子’都不碰到或者不需要单独配置的,这些项目在需要的时候再去查手册. 强调:配置的默认值不是在settings.py文件中!不要以为 ...

随机推荐

  1. 如何优雅的使用 Python 实现文件递归遍历

    今天有个脚本需要遍历获取某指定文件夹下面的所有文件,我记得很早前也实现过文件遍历和目录遍历的功能,于是找来看一看,嘿,不看不知道,看了吓一跳,原来之前我竟然用了这么搓的实现. 先发出来看看: def ...

  2. Java开发笔记(十六)非此即彼的条件分支

    前面花了大量篇幅介绍布尔类型及相应的关系运算和逻辑运算,那可不仅仅是为了求真值或假值,更是为了通过布尔值控制流程的走向.在现实生活中,常常需要在岔路口抉择走去何方,往南还是往北,向东还是向西?在Jav ...

  3. GNOME图形界面的基本操作

    成功登录进入CentOS系统之后,我们首先看到的桌面就是GNOME图形界面,下面来看一下相关的基本操作. 个性化设置 1,设置屏幕分辨率 进入菜单 2,更换桌面背景 进入下面菜单. 选择一张背景图片, ...

  4. 使用Git进行版本管理

    参考:http://www.runoob.com/git/git-tutorial.html 一.Git简介 1.Git 和 SVN 比较 (1)GIT是分布式的,SVN不是; (2)GIT把内容按元 ...

  5. MatrixTree速成

    前言 MatrixTree定理是用来解决生成树计数问题的有利工具 比如说这道题 MatrixTree定理的算法流程也非常简单 我们记矩阵\(A\)为无向图的度数矩阵 记矩阵\(D\)为无向图的邻接矩阵 ...

  6. Microsoft Dynamics CRM 9.0 OP 版本 安装 的那些 雷

    天天讲安装过程好无聊了,还是搞点有营养的东西来,那么后面来说说刚出来的MSCRM OP 9.0 版本安装的那些雷: 雷1:操作系统要求Windows 2016 Server 这点还好,因为之前安装MS ...

  7. bootstrap-treeview 在 bootstrap 4 不兼容解决办法及使用

    bootstrap-treeview 是bootstrap的一个树形插件,插件依赖: bootstrap/3.3.7 jquery/3.3.1 经过验证,它不可以在 bootstrap 高于 3.3. ...

  8. dede后台删除文章后台还有分页显示解决方法

    打开dede目录中content_list.php 大概在100行左右 $sql = "SELECT COUNT(*) AS dd FROM `#@__arctiny` $tinyQuery ...

  9. PJSUA2开发文档--第十一章 网络问题

    11 网络问题 11.1 IP地址更改 请参阅wiki 处理IP地址更改.请注意,本指南使用PJSUA API作为参考. 11.2 被阻止/过滤的网络 请参阅维基百科 通过阻止或过滤的VoIP网络

  10. Keepalived脑裂

    问题描述:开启防火墙后,Keepalived出现脑裂. 背景架构:两台centos7通过Keepalived实现高可用 问题具体表现形式:两台主机通过ip addr (ip  a)查看,发现两台主机都 ...