我不明白为什么你需要一个缓存连接,为什么不在每个请求都重新连接缓存用户凭据的地方,但无论如何我会尝试概述一个可能符合你要求的解决方案。
我建议先研究一个更通用的任务 - 在您的应用需要处理的后续请求之间缓存一些内容,并且无法序列化到 django 的会话中。
在您的特定情况下,此共享值将是一个 数据库连接(或多个连接)。
让我们从一个简单的任务开始,即在请求之间共享一个简单的计数器变量,以了解幕后实际发生的情况。
令人惊讶的是,两个答案都没有提到您可能使用的网络服务器!
实际上有多种方法可以在 Web 应用程序中处理并发连接:
- 拥有多个进程,每个请求都会随机进入其中一个
- 拥有多个线程,每个请求都由一个随机线程处理
- p.1 和 p.2 合并
- 各种 异步 技术,当有一个单个 进程 + 事件循环 处理请求时需要注意该请求处理程序不应长时间阻塞
根据我自己的经验,p.1-2 适用于大多数典型的 web 应用程序。
Apache1.x 只能处理 p.1,Apache2.x 可以处理所有 1-3。
让我们从以下django 应用程序开始,并运行一个单进程gunicorn 网络服务器。
我将使用gunicorn,因为它不像apache 那样容易配置(个人意见:-)
views.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')
urls.py
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.main, name='main'),
path('heavy/', views.heavy, name='heavy')
]
以单进程模式运行:
gunicorn testpool.wsgi -w 1
这是我们的进程树 - 只有 1 个工作人员可以处理所有请求
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
再次 - 只有 1 个进程
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) 像这样同步访问共享变量。
另一个问题是同一用户可能需要并行发出多个查询 - 即打开多个选项卡。
要处理它,当您有可用的数据库凭据时,您可以在第一次请求时打开 多个 连接。
如果用户需要的连接多于您的应用可能会等待锁定,直到连接可用。
回到你的问题
您可以创建一个具有以下方法的类:
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(...)
这不是一个完整的解决方案 - 您需要以某种方式删除长时间未使用的过时连接。
如果用户在很长一段时间后回来并且他的连接已关闭,他将需要再次提供他的凭据 - 希望从您的应用的角度来看没问题。
另外请记住 python threads 有其性能损失,不确定这是否对您来说是个问题。
我还没有检查过apache2(配置负担太大,我已经很久没有使用它并且通常使用uwsgi),但它也应该在那里工作 - 很高兴收到你的回复
如果你设法运行它)
也不要忘记 p.4(异步方法) - 您不太可能在 apache 上使用它,但值得研究 - 关键字:django + gevent,django + asyncio。它有其优点/缺点,并且可能会极大地影响您的应用实施,因此如果不详细了解您的应用需求,就很难提出任何解决方案