在线程中使用 Django ORM 并通过使用 BoundedSemaphore 避免“太多客户端”异常

Posted

技术标签:

【中文标题】在线程中使用 Django ORM 并通过使用 BoundedSemaphore 避免“太多客户端”异常【英文标题】:Using Django ORM in threads and avoiding "too many clients" exception by using BoundedSemaphore 【发布时间】:2011-03-27 00:39:00 【问题描述】:

我使用 manage.py 命令创建大约 200 个线程来检查远程主机。我的数据库设置允许我使用 120 个连接,所以我需要使用某种池。我试过使用分离的线程,像这样

class Pool(Thread):
    def __init__(self):
        Thread.__init__(self)        
        self.semaphore = threading.BoundedSemaphore(10)

    def give(self, trackers):
        self.semaphore.acquire()
        data = ... some ORM (not lazy, query triggered here) ...
        self.semaphore.release()
        return data

我将此对象的实例传递给每个检查线程,但在初始化 120 个线程后仍然在 Pool 对象中收到“OperationalError: FATAL: sorry, too many clients already”。 我预计只会打开 10 个数据库连接,并且线程将等待空闲信号量插槽。我可以通过注释“release()”来检查信号量是否有效,在这种情况下,只有 10 个线程可以工作,其他线程将等到应用程序终止。

据我所知,即使实际调用在不同的线程内,每个线程都在打开与数据库的新连接,但为什么呢?有什么方法可以只在一个线程内执行所有数据库查询?

【问题讨论】:

要找到实际的数据库问题,我想我们需要更多的 sql 代码。但是,你为什么不把整个线程池化,这样你一次只运行大约 20-30 个? 嗨,我的问题是 django 为每个接触任何 ORM 代码的线程创建连接,即使实际的 ORM 操作发生在不同的线程中。 (我已经通过从其他线程中删除所有代码来检查它,除了调用 Pool 的实例给定)。所以在我的情况下,除了获取数据之外没有 SQL。只要我有 class checker(Thread): def run(self): self.getter.give(self.trackers) 就会创建一个连接(trackers 是这里的字符串列表)。在达到 120 个连接的限制后,我的数据库开始出现异常。 顺便说一句,我能够以某种方式解决这个问题 - 通过在每个数据库请求后手动关闭连接,性能是可以接受的,但我仍然对这个问题的根源感到好奇。 【参考方案1】:

Django 的 ORM 在线程局部变量中管理数据库连接。所以每个访问 ORM 的不同线程都会创建自己的连接。你可以在django/db/backends/__init__.py的前几行看到。

如果要限制建立的数据库连接数,则必须限制实际访问 ORM 的不同线程的数量。一种解决方案可能是实现一个服务,将 ORM 请求委托给专用 ORM 线程池。要将请求及其结果从其他线程传输到其他线程,您必须实现某种消息传递机制。由于这是一个典型的生产者/消费者问题,关于线程的 Python 文档应该给出一些提示如何实现这一点。

编辑:我刚刚搜索了“django 连接池”。有很多人抱怨 Django 没有提供合适的连接池。他们中的一些人设法集成了一个单独的池包。对于 PostgreSQL,我会看看 pgpool 中间件。

【讨论】:

我能够通过两种方式解决这个问题,通过在这两种情况下使用 pgpool2 或带有手动 connection.close() 调用的信号量。 ORM 线程池是我首先尝试的,但由于某种原因,即使没有实际的 ORM,Django 也会为每个线程创建一个新连接,只要您尝试使用 ORM 访问任何不同的线程 - 这里就会出现新连接。附言pgpool 看起来是一个不错的解决方案,但我无法仅使用它来解决这个问题,因为我有一个长时间运行的应用程序(不是很多小应用程序)并且 pgpool 无法提供新的池连接,因为它们都被我的应用程序使用了跨度> 问题是:你在哪个线程上下文中调用Pool.give()?如果您调用从 Thread 继承的对象的方法,则该方法仍由调用线程执行,不一定由与该对象关联的线程执行。如果我正确解释了您之前的评论,则调用线程是您的检查线程。这意味着所有 200 个检查器线程都试图创建一个私有数据库连接(因为该连接是在线程本地存储的)。您的信号量仅限制并行 ORM 访问的数量,而不是建立的连接数。 从 1.6 左右开始,我们可以在 Django 中进行持久连接,看看docs.djangoproject.com/en/1.8/ref/settings/#conn-max-age

以上是关于在线程中使用 Django ORM 并通过使用 BoundedSemaphore 避免“太多客户端”异常的主要内容,如果未能解决你的问题,请参考以下文章

在 celery 任务中查询 Django ORM:SynchronousOnlyOperation:您不能从异步上下文中调用它 - 使用线程或 sync_to_async

使用php写入django ORM

Django ORM查询无法选择新对象

python---django中orm的使用admin配置与使用

使用原始 SQL 查询生成表,并希望在 Django 中将这些表用作 ORM

Django中ORM介绍