缓存/重用数据库连接以便以后查看usag

2024-04-26 14:49:40 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在保存用户的数据库连接。在他们第一次输入证书时,我会做如下操作:

self.conn = MySQLdb.connect (
    host = 'aaa',
    user = 'bbb',
    passwd = 'ccc',
    db = 'ddd',
    charset='utf8'
)
cursor = self.conn.cursor()
cursor.execute("SET NAMES utf8")
cursor.execute('SET CHARACTER SET utf8;')
cursor.execute('SET character_set_connection=utf8;')

然后,我准备好conn来处理用户的所有查询。但是,我不希望每次加载view时都重新连接。如何存储此“打开的连接”,以便在视图中执行以下操作:

^{pr2}$

更新:似乎上述操作不可能,也不是很好的实践,所以让我重新表述一下我正在尝试做的事情:

我有一个sql编辑器,用户在输入凭据后可以使用它(想想Navicat或SequelPro之类的东西)。注意这是不是默认的django db连接——我事先不知道凭据。现在,一旦用户“连接”了,我希望他们能够做任何他们想做的查询,而不是我必须重新连接每次他们这样做。例如——再次迭代——比如Navicat或SequelPro。如何使用python、django或mysql来实现呢?也许我真的不明白这里需要什么(缓存连接?连接池?因此,任何建议或帮助将不胜感激。在


Tags: django用户self数据库executedbutf8conn
3条回答

您可以使用IoC容器为您存储一个singleton提供程序。本质上,它不是每次都构造一个新的连接,而是只构造一次(第一次调用ConnectionContainer.connection_provider()),此后它将始终返回先前构造的连接。在

您需要dependency-injector包才能使我的示例正常工作:

import dependency_injector.containers as containers
import dependency_injector.providers as providers


class ConnectionProvider():
    def __init__(self, host, user, passwd, db, charset):
        self.conn = MySQLdb.connect(
            host=host,
            user=user,
            passwd=passwd,
            db=db,
            charset=charset
        )


class ConnectionContainer(containers.DeclarativeContainer):
    connection_provider = providers.Singleton(ConnectionProvider,
                                              host='aaa',
                                              user='bbb',
                                              passwd='ccc',
                                              db='ddd',
                                              charset='utf8')


def do_queries(request, sql):
    user = request.user
    conn = ConnectionContainer.connection_provider().conn
    cursor = conn.cursor()
    cursor.execute(sql)

我已经在这里对连接字符串进行了硬编码,但也可以根据可更改的配置使其可变。在这种情况下,您还可以为配置文件创建一个容器,并让连接容器从那里读取其配置。然后在运行时设置配置。具体如下:

^{pr2}$

我不明白为什么这里需要缓存连接,为什么不在每个请求上重新连接缓存用户的凭据,但是无论如何,我将尝试概述一个可能适合您的需求的解决方案。在

我建议先研究一个更通用的任务—在应用程序需要处理的后续请求之间缓存一些内容,并且不能序列化到django的会话中。 在您的特定情况下,这个共享值将是一个数据库连接(或多个连接)。 让我们从一个简单的任务开始,在请求之间共享一个简单的计数器变量,只是为了了解到底发生了什么。在

有趣的是,这两个答案都没有提到您可能使用的web服务器! 实际上,在web应用程序中有多种方法可以处理并发连接:

  1. 有多个进程,每个请求随机进入其中一个进程
  2. 有多个线程,每个请求都由一个随机的线程处理
  3. p、 1和p.2合并
  4. 当有一个单个进程+事件循环处理请求时,需要注意的是请求处理程序不应长时间阻塞

根据我自己的经验,p.1-2对于大多数典型的webapp来说都可以。 Apache1.x只能与p.1一起工作,Apache2.x可以处理所有1-3。在

让我们从下面的django应用程序开始,并运行单个进程gunicornweb服务器。 我将使用gunicorn,因为它很容易配置,不像apache(个人意见:-)

在视图.py

import time

from django.http import HttpResponse

c = 0

def main(self):
    global c
    c += 1
    return HttpResponse('val: {}\n'.format(c))


def heavy(self):
    time.sleep(10)
    return HttpResponse('heavy done')

在网址.py

^{pr2}$

以单进程模式运行:

gunicorn testpool.wsgi -w 1

这是我们的流程树-只有一个worker可以处理所有请求

pstree 77292
-+= 77292 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1
 \--- 77295 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1

尝试使用我们的应用程序:

curl 'http://127.0.0.1:8000'
val: 1

curl 'http://127.0.0.1:8000'
val: 2

curl 'http://127.0.0.1:8000'
val: 3

如您所见,您可以轻松地在后续请求之间共享计数器。 这里的问题是,您只能并行地处理单个请求。如果您在一个选项卡中请求/heavy//在完成/heavy之前将不起作用

现在让我们使用2个工作进程:

gunicorn testpool.wsgi -w 2

流程树如下所示:

 pstree 77285
-+= 77285 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 2
 |--- 77288 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 2
 \--- 77289 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 2

测试我们的应用程序:

curl 'http://127.0.0.1:8000'
val: 1

curl 'http://127.0.0.1:8000'
val: 2

curl 'http://127.0.0.1:8000'
val: 1

前两个请求由第一个worker process处理,第三个请求由第二个工作进程处理,后者有自己的内存空间,因此您可以看到1而不是3。 请注意,您的输出可能不同,因为过程1和2是随机选择的。但迟早你会遇到一个不同的过程。在

这对我们不是很有帮助,因为我们需要处理多个并发请求,而且我们需要以某种方式让请求由一个在一般情况下无法完成的特定进程来处理。在

大多数现成的技术只会缓存单个进程范围内的连接,如果您的请求由另一个进程提供服务,则需要建立一个新的连接。在

让我们转到线程

gunicorn testpool.wsgi -w 1 --threads 2

同样-只有一个过程

pstree 77310
-+= 77310 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1 --threads 2
 \--- 77313 oleg /Users/oleg/.virtualenvs/test3.4/bin/python /Users/oleg/.virtualenvs/test3.4/bin/gunicorn testpool.wsgi -w 1 --threads 2

现在,如果您在一个选项卡中运行/heavy,您仍然可以查询/,并且您的计数器将在请求之间被保留! 即使线程的数量根据您的工作负载而增加或减少,它仍然可以正常工作。在

问题:您需要使用python线程同步技术(read more)同步对共享变量的访问。 另一个问题是同一个用户可能需要并行发出多个查询,即打开多个选项卡。在

为了处理这个问题,当您有可用的db凭证时,您可以在第一个请求上打开多个连接。在

如果用户需要的连接数超过应用程序在锁定时等待的连接数,直到连接可用为止。在

回到你的问题

可以创建具有以下方法的类:

from contextlib import contextmanager

class ConnectionPool(object):

   def __init__(self, max_connections=4):
      self._pool = dict()
      self._max_connections = max_connections

   def preconnect(self, session_id, user, password):
       # create multiple connections and put them into self._pool
       # ...

    @contextmanager
    def get_connection(sef, session_id):
       # if have an available connection:
            # mark it as allocated
            # and return it
            try:
                yield connection
           finally:
              # put it back to the pool
              # ....
       # else
        # wait until there's a connection returned to the pool by another thread

pool = ConnectionPool(4)

def some_view(self):
     session_id = ...
     with pool.get_connection(session_id) as conn:
        conn.query(...)

这并不是一个完整的解决方案-您需要以某种方式删除过时的连接,而这些连接已经很久没有使用了。在

如果一个用户在很长一段时间后回来,并且他的连接已经关闭,他需要再次提供他的凭据-希望从你的应用程序的角度来看没问题。在

还要记住pythonthreads有其性能损失,不确定这是否是您的问题。在

我还没有检查过apache2(配置负担太重,我已经很久没有使用它了,通常使用uwsgi),但它应该也在那里工作-很高兴收到你的回音 如果你设法运行它)

另外,别忘了p.4(异步方法)-不太可能在apache上使用它,但是值得研究一下-关键字:django+geventdjango+asyncio。它有它的优缺点,可能会极大地影响你的应用程序的实现,所以在不详细了解你的应用程序需求的情况下,很难提出任何解决方案

在web应用程序上下文中同步执行这样的操作不是一个好主意。请记住,您的应用程序可能需要以多进程/线程的方式工作,并且您不能正常地在进程之间共享连接。因此,如果您在一个进程上为您的用户创建一个连接,就不能保证在同一个进程上接收到查询请求。一个更好的主意可能是让一个进程后台工作者处理多个线程(每个会话一个线程)中的连接,以便对数据库进行查询并在web应用程序上检索结果。应用程序应为每个会话分配一个唯一的ID,后台工作线程使用会话ID跟踪每个线程。您可以使用celery或任何其他支持异步结果的任务队列。所以设计如下:

             |<--|        |<--------------|                   |<--|
user (id: x) |   | webapp |   | queue |   | worker (thread x) |   | DB
             |-->|        |-->|       |-->|                   |-->|

您还可以为每个用户创建一个队列,直到他们有一个活动的会话,因此您可以为每个会话运行一个单独的后台进程。在

相关问题 更多 >