Django / WSGI应用程序中的持久数据库连接

时间:2015-12-09 07:24:59

标签: python django database-connection

我希望在django支持的Web应用程序中保持对第三方遗留数据库的持久连接。

user--inet--webserver--persistent-db-connection

我想保持Web应用程序和旧数据库之间的连接打开,因为对于这个特殊的数据库,创建新连接的速度非常慢。

它不像通常的连接池,因为我需要存储每个Web用户的连接。用户" Foo"需要在Web服务器和旧版DB之间建立自己的连接。

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

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

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

当然需要某种超时。如果没有来自用户的http请求" Foo" N分钟,然后持久连接需要关闭。

怎么能解决这个问题?

更新

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

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

连接数据库的代码

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

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

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

当然strangedb_connection无法序列化,也无法存储在会话中: - )

3 个答案:

答案 0 :(得分:3)

据我所知,你排除了这类问题的大多数(所有?)常见解决方案:

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

据我所知,实际上只有1' meta'解决方案,使用@ Gahbu建议的字典并保证给定user的请求转到同一个工作人员。即找出一种方法,每次都以相同的方式从User对象映射到给定的工作者(可能用工作人员的数量哈希他们的名字和MOD?)。

如果当前活动的用户全部映射到同一个工作程序,但是如果所有用户同时可能同时处于活动状态,那么此解决方案将无法充分利用您的N个工作者,那么应该同等传播。 (如果它们不是同样可能的话,那么映射可以能够解释它。)

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

<强> 1。编写自定义请求分配器

我并不熟悉apache / wsgi接口,但是......可能有可能替换Apache服务器中的组件,该组件使用一些自定义逻辑将HTTP请求分派给worker,这样就可以了总是派遣到同一个过程。

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

我不确定你是否可以在这里使用现成的包裹,但概念是:

  • 运行实现此功能的代理,将用户绑定到索引&#39;逻辑
  • 让代理将请求转发给Apache / wsgi网络服务器的N个副本之一,每个副本都有一个工作人员。

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

<强>摘要

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

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

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

答案 1 :(得分:3)

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

在你的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多路复用的解决方案可能会更好,但会更复杂,并且还需要您的&#34; strangedb&#34;库支持它,即它必须能够处理EAGAIN / EWOULDBLOCK并有能力在必要时重试系统调用。

Python中的多线程远比其他大多数语言都要危险,因为Python GIL实际上使得所有Python字节码都是线程安全的。

实际上,当底层C代码使用Py_BEGIN_ALLOW_THREADS宏时,线程只会并发运行,multiprocessing宏与其对应的Py_END_ALLOW_THREADS通常包含在系统调用和CPU密集型操作中。 / p>

这样做的好处是,在Python代码中几乎不可能发生线程冲突,但缺点是它不会总是在一台机器上最佳地使用多个CPU内核。 / p>

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

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

  

......围绕线程的GIL限制似乎是一个问题。   这不是趋势分开使用的原因之一   而不是过程?

这很可能是Python undocumented模块存在的主要原因,即提供跨多个CPU核心的Python字节码的并发执行,尽管存在The C10K problem {该模块中的{1}}类,它使用线程而不是进程。

&#34; GIL限制&#34;在你真正需要利用每个CPU核心上的每个CPU周期的情况下肯定会出现问题,例如:如果你正在编写一个必须以高清晰度每秒渲染60帧的计算机游戏。

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

最终,它在性能和可维护性之间进行权衡,并且考虑到硬件通常比开发人员便宜得多,因此支持可维护性而不是性能通常更具成本效益。

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

有关扩展基于Web的服务的技术比较,另请参阅{{3}}。

答案 2 :(得分:0)

执行此操作的一种简单方法是让另一个python进程管理持久连接池(每个用户一个,并在需要时可以超时)。然后另一个python进程和django可以像zeromq一样快速地进行通信。 interprocess communication in python