[英]How to share (initialize and close) aiohttp.ClientSession between Django async views to use connection pooling
Django 从 3.1 版开始支持异步视图,因此它非常适合非阻塞调用外部 HTTP API(例如使用aiohttp )。
我经常看到下面的代码示例,我认为它在概念上是错误的(尽管它工作得很好):
import aiohttp
from django.http import HttpRequest, HttpResponse
async def view_bad_example1(request: HttpRequest):
async with aiohttp.ClientSession() as session:
async with session.get("https://example.com/") as example_response:
response_text = await example_response.text()
return HttpResponse(response_text[:42], content_type="text/plain")
此代码为每个传入请求创建一个ClientSession
,这是低效的。 aiohttp
不能使用例如连接池。
不要为每个请求创建 session。 很可能每个应用程序都需要一个 session 来完全执行所有请求。
来源: https://docs.aiohttp.org/en/stable/client_quickstart.html#make-a-request
这同样适用于 httpx:
另一方面,客户端实例使用 HTTP 连接池。 这意味着当您向同一主机发出多个请求时,客户端将重用底层 TCP 连接,而不是为每个请求重新创建一个。
资料来源: https://www.python-httpx.org/advanced/#why-use-a-client
有没有办法在 Django 中全局实例化aiohttp.ClientSession
以便可以跨多个请求共享此实例? 不要忘记必须在正在运行的事件循环中创建ClientSession
( 为什么在事件循环之外创建 ClientSession 很危险? ),所以我们不能实例化它,例如在 Django 设置中或作为模块级变量。
我得到的最接近的是这段代码。 但是,我认为这段代码很丑陋,并且没有解决例如关闭 session 的问题。
CLIENT_SESSSION = None
async def view_bad_example2(request: HttpRequest):
global CLIENT_SESSSION
if not CLIENT_SESSSION:
CLIENT_SESSSION = aiohttp.ClientSession()
example_response = await CLIENT_SESSSION.get("https://example.com/")
response_text = await example_response.text()
return HttpResponse(response_text[:42], content_type="text/plain")
基本上,我正在寻找来自 FastAPI 的等效事件,可用于在异步上下文中创建/关闭某些资源。
顺便说一下,这是两个视图之间使用 k6 的性能比较:
view_bad_example1
: avg=1.32s min=900.86ms med=1.14s max=2.22sp(90)=2s p(95)=2.1s
view_bad_example2
: avg=930.82ms min=528.28ms med=814.31ms max=1.66sp(90)=1.41sp(95)=1.52s
Django 没有实现 ASGI Lifespan 协议。
参考: https://github.com/django/django/pull/13636
小星星会。 FastAPI 直接使用 Starlette 的事件处理程序实现。
以下是使用 Django 实现这一目标的方法:
ASGIHandler
的子类中实现 ASGI Lifespan 协议。import django
from django.core.asgi import ASGIHandler
class MyASGIHandler(ASGIHandler):
def __init__(self):
super().__init__()
self.on_shutdown = []
async def __call__(self, scope, receive, send):
if scope['type'] == 'lifespan':
while True:
message = await receive()
if message['type'] == 'lifespan.startup':
# Do some startup here!
await send({'type': 'lifespan.startup.complete'})
elif message['type'] == 'lifespan.shutdown':
# Do some shutdown here!
await self.shutdown()
await send({'type': 'lifespan.shutdown.complete'})
return
await super().__call__(scope, receive, send)
async def shutdown(self):
for handler in self.on_shutdown:
if asyncio.iscoroutinefunction(handler):
await handler()
else:
handler()
def my_get_asgi_application():
django.setup(set_prefix=False)
return MyASGIHandler()
application
。# application = get_asgi_application()
application = my_get_asgi_application()
get_client_session
来共享实例:import asyncio
import aiohttp
from .asgi import application
CLIENT_SESSSION = None
_lock = asyncio.Lock()
async def get_client_session():
global CLIENT_SESSSION
async with _lock:
if not CLIENT_SESSSION:
CLIENT_SESSSION = aiohttp.ClientSession()
application.on_shutdown.append(CLIENT_SESSSION.close)
return CLIENT_SESSSION
用法:
async def view(request: HttpRequest):
session = await get_client_session()
example_response = await session.get("https://example.com/")
response_text = await example_response.text()
return HttpResponse(response_text[:42], content_type="text/plain")
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.