【问题标题】:Persistent DB Connection in Django/WSGI applicationDjango/WSGI 应用程序中的持久数据库连接
【发布时间】:2016-03-14 09:08:44
【问题描述】:

我想在 django 驱动的 Web 应用程序中保持与第三方遗留数据库的持久连接。

我想保持网络应用和旧数据库之间的连接打开,因为对于这个特殊的数据库来说,创建新连接非常慢。

这与通常的连接池不同,因为我需要存储每个 Web 用户的连接。用户“Foo”需要在 Web 服务器和旧版数据库之间建立自己的连接。

到目前为止,我使用 Apache 和 wsgi,但如果其他解决方案更合适,我可以更改。

到目前为止,我使用 django。在这里我也可以改变。但是痛苦会更大,因为已经有很多代码需要再次集成。

到目前为止,我使用 Python。我想 Node.js 更适合这里,但改变的痛苦太高了。

当然需要某种超时。如果 N 分钟内没有来自用户“Foo”的 http-request,则需要关闭持久连接。

如何解决?

更新

我称之为DB,但它不是通过settings.DATABASES 配置的数据库。这是一个奇怪的、遗留的、不广泛传播的类似 DB 的系统,我需要集成。

如果此时我有 50 个人在线使用 Web 应用,那么我需要有 50 个持久连接。每个用户一个。

连接数据库的代码

我可以在每个请求中执行这一行:

strangedb_connection = strangedb.connect(request.user.username)

但是这个操作很慢。使用连接速度很快。

当然strangedb_connection 不能被序列化并且不能存储在会话中:-)

【问题讨论】:

  • 您可以在 django 和 db 之间开发一个小型 python 应用程序以 24/7 运行,但这有一些软肋。
  • @xyres strangedb 包含太多数据。记不住了。
  • 我建议使用SQLite backend ORM code 作为为您的自定义数据库编写自己的模式(我们无法为您编写,因为我们不知道它是什么、数据类型等。 - 你没有提供足够的信息来这样做)。但是,使用此作为模式,您可以在所有方法共享的__init__() 方法中实例化一个连接。然后编写处理查询和写入的自定义方法(类似于SQLiteCursorWrapper,但特定于您的数据库类型,仅共享单个连接)。
  • @BobDylan strangedb 是数据库,但不是关系数据库。它需要每个用户自己的连接。有了这些参数,我猜你的解决方案不起作用。如果是,请详细说明。 ...你的伍迪·格思里 :-)
  • 您是否尝试将此连接存储在字典中?不是在本地环境中,而是在全球环境中?缺点:您必须检查它是否存在此用户的打开连接,您必须在不使用时手动关闭连接,并且此连接不会在 Django 实例之间共享(您需要做进一步的工作才能拥有它可以正确扩展并避免同时运行过多的连接)...

标签: python django database-connection


【解决方案1】:

管理连接的工作守护进程

您的图片目前看起来像:

user  ----------->  webserver  <--------[1]-->  3rd party DB

connection [1] is expensive.

你可以解决这个问题:

user ---->  webserver  <--->  task queue[1]  <--->  worker daemon  <--[2]-> 3rd party DB

[1] task queue can be redis, celery or rabbitmq.
[2] worker daemon keeps connection open.

工作守护进程将连接到第 3 方数据库并保持连接打开。这意味着每个请求都不必支付连接费用。任务队列将是进程间通信,将工作分派给守护进程并在第 3 方数据库中执行查询。网络服务器在处理方面应尽可能轻,并让工作人员完成昂贵的任务。

使用 apache + modwsgi 预加载

您实际上可以preload 并在第一次请求之前完成昂贵的连接。这是通过WSGIImportScript configuration directive 完成的。我不记得有预加载+分叉配置是否意味着每个请求都已经打开连接并共享它;但由于您拥有大部分代码,这可能是一个简单的实验。

使用 uwsgi 预加载

uwsgi 也支持预加载。这是通过the import directive 完成的。

【讨论】:

  • 我不知道谁对你的回答投了反对票。那不是我。我也想知道原因。乍一看,您的解决方案看起来很复杂。但我比基于线程的解决方案更喜欢它。谢谢你的回答。
  • 我做到了,但改为支持。最初的答案非常注重细节,因为编辑更有意义。
【解决方案2】:

据我所知,您已经排除了大多数(全部?)此类问题的常见解决方案:

  • 将连接存储在字典中...需要 N 个工作人员,并且无法保证哪个请求发送给哪个工作人员
  • 将数据存储在缓存中...数据过多
  • 在缓存中存储连接信息...连接不可序列化

据我所知,实际上只有 1 个“元”解决方案,请使用 @Gahbu 的字典建议并保证对给定 user 的请求会发送给同一个工作人员。 IE。想办法每次都以相同的方式从 User 对象映射到给定的工作人员(也许按工作人员的数量散列他们的姓名和 MOD?)。

如果当前活动的用户都映射到同一个工作人员,则此解决方案不会充分利用您的 N 个工作人员,但如果所有用户同时处于活动状态的可能性相同,那么工作应该 均匀分布。 (如果它们的可能性不同,那么映射可能能够解释这一点。

我能想到的两种可能的方法是:

1.编写自定义请求分配器

我对 apache/wsgi 接口领域并不十分熟悉,但是……可以用一些自定义逻辑替换 Apache 服务器中将 HTTP 请求分派给工作人员的组件,以便它始终分派到同一个过程。

2。在 N 个单线程工作人员面前运行负载均衡器/代理

我不确定您是否可以在这里使用准备就绪的包,但概念是:

  • 运行实现此“将用户绑定到索引”逻辑的代理
  • 让代理将请求转发到您的 Apache/wsgi 网络服务器的 N 个副本之一,每个副本都有一个工作器。

注意:我在这里遇到的第二个想法:https://github.com/benoitc/gunicorn/issues/183

总结

对于这两个选项,您现有应用程序中的实现都非常简单。您的应用程序只是更改为使用字典来存储持久连接(如果还没有,则创建一个)。在开发中测试单个实例与在生产中相同。在生产环境中,实例本身并不明智,因为它们总是被问及相同的用户。

我喜欢这里的选项 2,原因如下:

  • 也许有一个现有的服务器包允许您定义这种代理技巧
  • 如果没有,创建一个自定义代理应用程序以放在当前应用程序前面可能不会太难(特别是考虑到当请求到达 strangedb 服务时您(已经)受到的限制)

【讨论】:

  • 看起来不错。在这种情况下,“将用户绑定到索引”是什么意思?
  • 在我的脑海中,有一些“函数”接受用户的散列(或他们的会话密钥或唯一的东西),然后按数字 N(单线程工作者)对其进行 MOD 生成哪个要派遣到的工人的“索引”。有意义吗?
  • @Doddle 感谢您的澄清,现在我明白您对“索引”的意思了
【解决方案3】:

您可以使用WSGIDaemonProcess 指令来拥有多个在单个进程中运行的工作线程,而不是拥有多个工作进程。这样,所有线程都可以共享同一个数据库连接映射。

在你的 apache 配置中有这样的东西......

# mydomain.com.conf

<VirtualHost *:80>

    ServerName mydomain.com
    ServerAdmin webmaster@mydomain.com

    <Directory />
        Require all granted
    </Directory>

    WSGIDaemonProcess myapp processes=1 threads=50 python-path=/path/to/django/root display-name=%{GROUP}
    WSGIProcessGroup myapp
    WSGIScriptAlias / /path/to/django/root/myapp/wsgi.py

</VirtualHost>

...然后您可以在您的 Django 应用程序中使用像这样简单的东西...

# views.py

import thread
from django.http import HttpResponse

# A global variable to hold the connection mappings
DB_CONNECTIONS = {}

# Fake up this "strangedb" module
class strangedb(object):

    class connection(object):
        def query(self, *args):
            return 'Query results for %r' % args

    @classmethod
    def connect(cls, *args):
        return cls.connection()


# View for homepage
def home(request, username='bob'):

    # Remember thread ID
    thread_info = 'Thread ID = %r' % thread.get_ident()

    # Connect only if we're not already connected
    if username in DB_CONNECTIONS:
        strangedb_connection = DB_CONNECTIONS[username]
        db_info = 'We reused an existing connection for %r' % username
    else:
        strangedb_connection = strangedb.connect(username)
        DB_CONNECTIONS[username] = strangedb_connection
        db_info = 'We made a connection for %r' % username

    # Fake up some query
    results = strangedb_connection.query('SELECT * FROM my_table')

    # Fake up an HTTP response
    text = '%s\n%s\n%s\n' % (thread_info, db_info, results)
    return HttpResponse(text, content_type='text/plain')

...在第一次命中时产生...

Thread ID = 140597557241600
We made a connection for 'bob'
Query results for 'SELECT * FROM my_table'

...然后,在第二个...

Thread ID = 140597145999104
We reused an existing connection for 'bob'
Query results for 'SELECT * FROM my_table'

显然,当不再需要数据库连接时,您需要添加一些东西来拆除它们,但是如果没有更多关于您的应用应该如何工作的信息,就很难知道最好的方法。

更新 #1:关于 I/O 多路复用与多线程

我在生活中使用了两次线程,每次都是 恶梦。大量时间浪费在调试不可复现上 问题。我认为是事件驱动和非阻塞 I/O 架构 可能更扎实。

使用 I/O 多路复用的解决方案可能会更好,但会更复杂,并且还需要您的“strangedb”库来支持它,即它必须能够处理 EAGAIN/EWOULDBLOCK 和有能力在必要时重试系统调用。

由于 Python 的GIL,Python 中的多线程远没有其他大多数语言那么危险,从本质上讲,它使所有 Python 字节码都是线程安全的。

实际上,只有在底层 C 代码使用 Py_BEGIN_ALLOW_THREADS 宏时,线程才会并发运行,该宏与其对应的 Py_END_ALLOW_THREADS 通常围绕系统调用和 CPU 密集型操作进行包装。

这样做的好处是在 Python 代码中几乎不可能发生线程冲突,但坏处是它并不总是能在单台机器上充分利用多个 CPU 内核。

我建议上述解决方案的原因是它相对简单,并且只需要很少的代码更改,但是如果您可以详细说明您的“strangedb”库,可能会有更好的选择。拥有一个需要每个并发用户单独的网络连接的数据库似乎很奇怪。

更新 #2:关于多处理与多线程

...围绕线程的 GIL 限制似乎有点问题。 这难道不是趋势是使用分离的原因之一吗? 代替进程?

这很可能是 Python 的 multiprocessing 模块存在的主要原因,即提供跨多个 CPU 内核并发执行 Python 字节码,尽管该模块中有一个 undocumented ThreadPool 类,它使用线程而不是进程。

在您确实需要利用每个 CPU 内核上的每个 CPU 周期的情况下,“GIL 限制”肯定会出现问题,例如如果您正在编写一个必须以每秒 60 帧的速度渲染高清的电脑游戏。

但是,大多数基于 Web 的服务可能会花费大部分时间等待某事发生,例如网络 I/O 或磁盘 I/O,Python 线程将允许同时发生。

归根结底,这是性能和可维护性之间的权衡,并且鉴于硬件通常比开发人员的时间便宜得多,因此优先考虑可维护性而不是性能通常更具成本效益。

坦率地说,当您决定使用虚拟机语言(例如 Python)而不是可编译成真实机器代码的语言(例如 C)时,您就已经准备好牺牲一些性能换取方便。

另请参阅The C10K problem,了解扩展基于 Web 的服务的技术比较。

【讨论】:

  • 假设从工作线程切换到线程没有问题(唯一的事情是如果一个线程终止进程,所有其他线程也会死掉,但考虑到其他约束,这不是真正的问题),这听起来像最好的解决方案!
  • Threads ... 我的内心陷入了 FUD(恐惧、不确定性和怀疑)。我在生活中使用了两次线程,每次都是一场噩梦。大量时间浪费在调试不可重现的问题上。我认为事件驱动和非阻塞 I/O 架构可能更可靠。但这只是我的感觉,并不是一个确凿的事实。
  • 我对 python 游戏有点陌生,但 GIL 对线程的限制似乎有点问题。这难道不是趋势是使用单独的流程的原因之一吗?
【解决方案4】:

一种简单的方法是让另一个 python 进程管理持久连接池(每个用户一个,并且可以在需要时超时)。然后其他 python 进程和 django 可以与 zeromq 之类的快速通信。 interprocess communication in python

【讨论】:

    猜你喜欢
    • 2010-11-10
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 2018-05-22
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多