简体   繁体   English

如何在 Django 异步视图之间共享(初始化和关闭)aiohttp.ClientSession 以使用连接池

[英]How to share (initialize and close) aiohttp.ClientSession between Django async views to use connection pooling

Django supportsasync views since version 3.1, so it's great for non-blocking calls to eg external HTTP APIs (using, for example, aiohttp ). Django 从 3.1 版开始支持异步视图,因此它非常适合非阻塞调用外部 HTTP API(例如使用aiohttp )。

I often see the following code sample, which I think is conceptually wrong (although it works perfectly fine):经常看到下面的代码示例,我认为它在概念上是错误的(尽管它工作得很好):

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")

This code creates a ClientSession for each incoming request, which is inefficient.此代码为每个传入请求创建一个ClientSession ,这是低效的。 aiohttp cannot then use eg connection pooling. aiohttp不能使用例如连接池。

Don't create a session per request.不要为每个请求创建 session。 Most likely you need a session per application which performs all requests altogether.很可能每个应用程序都需要一个 session 来完全执行所有请求。

Source: https://docs.aiohttp.org/en/stable/client_quickstart.html#make-a-request来源: https://docs.aiohttp.org/en/stable/client_quickstart.html#make-a-request

The same applies to httpx:这同样适用于 httpx:

On the other hand, a Client instance uses HTTP connection pooling.另一方面,客户端实例使用 HTTP 连接池。 This means that when you make several requests to the same host, the Client will reuse the underlying TCP connection, instead of recreating one for every single request.这意味着当您向同一主机发出多个请求时,客户端将重用底层 TCP 连接,而不是为每个请求重新创建一个。

Source: https://www.python-httpx.org/advanced/#why-use-a-client资料来源: https://www.python-httpx.org/advanced/#why-use-a-client

Is there any way to globally instantiate aiohttp.ClientSession in Django so that this instance can be shared across multiple requests?有没有办法在 Django 中全局实例化aiohttp.ClientSession以便可以跨多个请求共享此实例? Don't forget that ClientSession must be created in a running eventloop ( Why is creating a ClientSession outside of an event loop dangerous? ), so we can't instantiate it eg in Django settings or as a module-level variable.不要忘记必须在正在运行的事件循环中创建ClientSession为什么在事件循环之外创建 ClientSession 很危险? ),所以我们不能实例化它,例如在 Django 设置中或作为模块级变量。

The closest I got is this code.我得到的最接近的是这段代码。 However, I think this code is ugly and doesn't address eg closing the session.但是,我认为这段代码很丑陋,并且没有解决例如关闭 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")

Basically I'm looking for the equivalent of Events from FastAPI that can be used to create/close some resource in an async context.基本上,我正在寻找来自 FastAPI 的等效事件,可用于在异步上下文中创建/关闭某些资源

By the way here is a performance comparison using k6 between the two views:顺便说一下,这是两个视图之间使用 k6 的性能比较:

  • view_bad_example1 : avg=1.32s min=900.86ms med=1.14s max=2.22sp(90)=2s p(95)=2.1s 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 view_bad_example2 : avg=930.82ms min=528.28ms med=814.31ms max=1.66sp(90)=1.41sp(95)=1.52s

Django doesn't implement the ASGI Lifespan protocol. Django 没有实现 ASGI Lifespan 协议。
Ref: https://github.com/django/django/pull/13636参考: https://github.com/django/django/pull/13636

Starlette does .小星星 FastAPI directly uses Starlette's implementation of event handlers. FastAPI 直接使用 Starlette 的事件处理程序实现。

Here's how you can achieve that with Django:以下是使用 Django 实现这一目标的方法:

  1. Implement the ASGI Lifespan protocol in a subclass of Django's ASGIHandler .在 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()
  1. Replace the application in asgi.py.替换 asgi.py 中的application
# application = get_asgi_application()
application = my_get_asgi_application()
  1. Implement a helper get_client_session to share the instance:实现一个助手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

Usage:用法:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM