上周对我们用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. [leetcode](4.21)2. 按字典序排列最小的等效字符串

    给出长度相同的两个字符串:A 和 B,其中 A[i] 和 B[i] 是一组等价字符.举个例子,如果 A = "abc" 且 B = "cde",那么就有 'a' ...

  2. OO Homework One Notes

    系数/指数 过大 溢出(True) eg. - +999999999454554999999 * x ^ -85858554545454545548585858858 x前存在两个符号,与x有空格分离 ...

  3. 15 , CSS 背景与列表

    1.CSS 中背景的使用 2.CSS 中列表的使用 15.1 CSS 中背景的使用 属性名称 属性值 说明 background-attachment scroll 设置背景图像会随视窗滚动 条的移动 ...

  4. SAP MM 预留单据里的Base date和Requirement date

    SAP MM 预留单据里的Base date和Requirement date Base date可以在预留创建的初始界面指定, 这个日期可以作为预留各个行项目默认的requirement date. ...

  5. Android远程桌面助手(Build 0662)

    ARDC Build 0662, Jul 19, 2017 OPT: 1440*2560及以下分辨率设备,帧速能稳定在20帧~25帧 FIX: 拖拽文件的路径中包含空格的处理 ADD: 支持Ctrl+ ...

  6. C++ 重点关键字

    const 四种用法 1.修饰变量起到限定只读作用: void func(const int a, const string str) {...} const int* func(...) {...} ...

  7. 一幅图,看懂中国CMMI

    以下数据由Fancier凡奉信息根据历年CMMI Institute公布的CMMI评估结果的汇总整理.数据跨度2008-2017年,包含对中国CMMI与全球CMMI的不同等级.版本的统计.

  8. 推荐一款MongoDB的客户端管理工具--nosqlbooster

    今天给大家推荐一款MongoDB的客户端工具--nosqlbooster,这个也是我工作中一直使用的连接管理MongoDB的工具.这个工具还有个曾用名--mongobooster.nosqlboost ...

  9. Oracle获取表字段名,字段类型,字段长度,注释

    SELECT b.comments as 注释, a.column_name as 列名, a.data_type || '(' || a.data_length || ')' as 数据类型, a. ...

  10. .NET 术语

    .NET 术语 1. AOT 预编译器.与 JIT 类似,此编译器还可将 IL 转换为机器代码. 与 JIT 编译相比,AOT 编译在应用程序执行前进行并且通常在不同计算机上执行. 由于在运行时 AO ...