我将 django 与 apache、mod_wsgi 和 PostgreSQL(都在同一主机上)一起使用,我需要处理很多简单的动态页面请求(每秒数百个)。我遇到的问题是瓶颈是 django 没有持久的数据库连接并且在每个请求上都重新连接(这需要将近 5 毫秒)。在进行基准测试时,我通过持久连接得到了这个结果,我可以处理接近 500 r/s 而没有我只能得到 50 r/s。
有人有什么建议吗?如何修改 Django 以使用持久连接或加快从 Python 到 DB 的连接?
我将 django 与 apache、mod_wsgi 和 PostgreSQL(都在同一主机上)一起使用,我需要处理很多简单的动态页面请求(每秒数百个)。我遇到的问题是瓶颈是 django 没有持久的数据库连接并且在每个请求上都重新连接(这需要将近 5 毫秒)。在进行基准测试时,我通过持久连接得到了这个结果,我可以处理接近 500 r/s 而没有我只能得到 50 r/s。
有人有什么建议吗?如何修改 Django 以使用持久连接或加快从 Python 到 DB 的连接?
Django 1.6添加了持久连接支持(最新稳定 Django 的文档链接):
持久连接避免了在每个请求中重新建立与数据库的连接的开销。它们由定义连接的最大生命周期的 CONN_MAX_AGE 参数控制。可以为每个数据库独立设置。
试试PgBouncer - 一个用于 PostgreSQL 的轻量级连接池。特征:
在 Django 主干中,编辑django/db/__init__.py
并注释掉该行:
signals.request_finished.connect(close_connection)
此信号处理程序导致它在每次请求后断开与数据库的连接。我不知道这样做的所有副作用是什么,但是在每次请求后开始新连接没有任何意义;正如您所注意到的,它会破坏性能。
我现在正在使用它,但我还没有完成一整套测试来查看是否有任何问题。
我不知道为什么每个人都认为这需要一个新的后端或一个特殊的连接池或其他复杂的解决方案。这看起来很简单,尽管我不怀疑有一些晦涩难懂的陷阱让他们首先这样做——应该更明智地处理这些问题;正如您所注意到的,对于高性能服务而言,每个请求的 5 毫秒开销是相当多的。(我花了150 毫秒——我还没弄清楚为什么。)
编辑:另一个必要的改变是在 django/middleware/transaction.py;删除两个 transaction.is_dirty() 测试并始终调用 commit() 或 rollback()。否则,如果它只从数据库中读取,它将不会提交事务,这将使应该关闭的锁处于打开状态。
我创建了一个小的Django 补丁,它通过 sqlalchemy 池实现 MySQL 和 PostgreSQL 的连接池。
这在很长一段时间内非常适合http://grandcapital.net/的生产。
该补丁是在谷歌搜索主题后编写的。
免责声明:我没有尝试过这个。
我相信您需要实现一个自定义数据库后端。网上有几个例子展示了如何使用连接池实现数据库后端。
对于您的情况,使用连接池可能是一个很好的解决方案,因为当连接返回池时,网络连接保持打开状态。
两篇文章都使用 MySQL - 也许您可以在 Postgresql 中使用类似的技术。
编辑:
我制作了一些小的自定义 psycopg2 后端,它使用全局变量实现持久连接。有了这个,我能够将每秒请求的数量从 350 提高到 1600(在非常简单的页面上,几乎没有选择)只需将其保存base.py
在任何目录(例如 postgresql_psycopg2_persistent)中调用的文件中并在设置中设置
DATABASE_ENGINE 到 projectname.postgresql_psycopg2_persistent
笔记!!!该代码不是线程安全的-由于意外结果,您不能将其与 python 线程一起使用,如果是 mod_wsgi,请使用线程 = 1 的 prefork 守护程序模式
# Custom DB backend postgresql_psycopg2 based
# implements persistent database connection using global variable
from django.db.backends.postgresql_psycopg2.base import DatabaseError, DatabaseWrapper as BaseDatabaseWrapper, \
IntegrityError
from psycopg2 import OperationalError
connection = None
class DatabaseWrapper(BaseDatabaseWrapper):
def _cursor(self, *args, **kwargs):
global connection
if connection is not None and self.connection is None:
try: # Check if connection is alive
connection.cursor().execute('SELECT 1')
except OperationalError: # The connection is not working, need reconnect
connection = None
else:
self.connection = connection
cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs)
if connection is None and self.connection is not None:
connection = self.connection
return cursor
def close(self):
if self.connection is not None:
self.connection.commit()
self.connection = None
或者这是一个线程安全的线程,但 python 线程不使用多个内核,因此您不会像以前的线程那样获得性能提升。您也可以将这个与多进程之一一起使用。
# Custom DB backend postgresql_psycopg2 based
# implements persistent database connection using thread local storage
from threading import local
from django.db.backends.postgresql_psycopg2.base import DatabaseError, \
DatabaseWrapper as BaseDatabaseWrapper, IntegrityError
from psycopg2 import OperationalError
threadlocal = local()
class DatabaseWrapper(BaseDatabaseWrapper):
def _cursor(self, *args, **kwargs):
if hasattr(threadlocal, 'connection') and threadlocal.connection is \
not None and self.connection is None:
try: # Check if connection is alive
threadlocal.connection.cursor().execute('SELECT 1')
except OperationalError: # The connection is not working, need reconnect
threadlocal.connection = None
else:
self.connection = threadlocal.connection
cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs)
if (not hasattr(threadlocal, 'connection') or threadlocal.connection \
is None) and self.connection is not None:
threadlocal.connection = self.connection
return cursor
def close(self):
if self.connection is not None:
self.connection.commit()
self.connection = None
这是 django 连接池的一个包: django-db-connection-pool
pip install django-db-connection-pool
您可以提供其他选项以传递给 SQLAlchemy 的池创建,键的名称是 POOL_OPTIONS:
DATABASES = {
'default': {
...
'POOL_OPTIONS' : {
'POOL_SIZE': 10,
'MAX_OVERFLOW': 10
}
...
}
}