Uvicorn async workers по-прежнему работают синхронно

Вопрос вкратце

Я перевел свой проект с Django 2.2 на Django 3.2, и теперь хочу начать использовать возможность асинхронных представлений. Я создал асинхронное представление, настроил конфигурацию asgi и запустил gunicorn с рабочим Uvicorn. При одновременной загрузке этого сервера 10 пользователями, они обслуживаются синхронно. Что мне нужно настроить, чтобы обслуживать 10 одновременных пользователей асинхронным представлением?

Вопрос в деталях

Вот что я сделал на данный момент в своей локальной среде:

  • Я работаю с Django 3.2.10 и Python 3.9.
  • Я установил gunicorn и uvicorn через pip
  • Я создал asgi.py файл со следующим содержимым
    import os
    from django.core.asgi import get_asgi_application
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyService.settings.local')
    application = get_asgi_application()
  • Я создал представление со следующей реализацией, и подключил его в urlpatterns:
    import asyncio
    import json
    from django.http import HttpResponse
    
    async def async_sleep(request):
        await asyncio.sleep(1)
        return HttpResponse(json.dumps({'mode': 'async', 'time': 1).encode())
  • Я запускаю локально сервер gunicorn с рабочим Uvicorn:
gunicorn BudgetService.asgi:application -k uvicorn.workers.UvicornWorker
[2022-01-26 14:37:14 +0100] [8732] [INFO] Starting gunicorn 20.1.0
[2022-01-26 14:37:14 +0100] [8732] [INFO] Listening at: http://127.0.0.1:8000 (8732)
[2022-01-26 14:37:14 +0100] [8732] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2022-01-26 14:37:14 +0100] [8733] [INFO] Booting worker with pid: 8733
[2022-01-26 13:37:15 +0000] [8733] [INFO] Started server process [8733]
[2022-01-26 13:37:15 +0000] [8733] [INFO] Waiting for application startup.
[2022-01-26 13:37:15 +0000] [8733] [INFO] ASGI 'lifespan' protocol appears unsupported.
[2022-01-26 13:37:15 +0000] [8733] [INFO] Application startup complete.
  • Я обратился к API с локального клиента один раз. Через 1 секунду я получаю 200 OK, как и ожидалось.
  • .
  • Я настроил сервер locust для порождения одновременных пользователей. Когда я позволяю ему делать запросы с 1 одновременным пользователем, каждые 1 секунду завершается вызов API.
  • .
  • Когда я позволяю ему делать запросы с 10 одновременными пользователями, каждые 1 секунду вызов API завершается. Все остальные запросы ожидают.
  • .

Последняя вещь - это не то, чего я ожидаю. Я ожидаю, что рабочий во время асинхронного сна будет уже принимать следующий запрос. Может быть, я упустил какую-то конфигурацию?

Я также пробовал использовать Дафну вместо Увикорна, но с тем же результатом.

При выполнении команды gunicorn можно попробовать добавить параметр workers с помощью опций -w или --workers.

По умолчанию установлено значение 1, как указано в документации gunicorn. Вы можете попробовать увеличить это значение.

Пример использования:

gunicorn MyService.asgi:application -k uvicorn.workers.UvicornWorker -w 10

Кроме того, вам может понадобиться проверить другие рабочие атрибуты в документации, такие как worker_class и threads

Ваш ApiLoggerMiddleware является синхронным промежуточным программным обеспечением.

Из https://docs.djangoproject.com/en/4.0/topics/async/#async-views, выделение мое:

Вы получите преимущества полностью асинхронного стека запросов, только если на вашем сайте нет синхронного промежуточного ПО. Если есть синхронное промежуточное ПО, то Django должен использовать поток на запрос, чтобы безопасно эмулировать синхронную среду для него.

.

Программное обеспечение промежуточного слоя может быть построено для поддержки как синхронных, так и асинхронных контекстов. Некоторые из промежуточных программ Django построены подобным образом, но не все. Чтобы увидеть, какое промежуточное ПО Django должно быть адаптировано, вы можете включить отладочную регистрацию для логгера django.request и поискать в логе сообщения о "Synchronous middleware ... adapted".

(Сообщение журнала в настоящее время гласит "Asynchronous middleware ... adapted", об ошибке сообщается на #33495.)

Включите отладочную регистрацию для регистратора django.request, добавив следующее к настройкам LOGGING:

'django.request': {
    'handlers': ['console'],
    'level': 'DEBUG',
},

Решение

Чтобы сделать ApiLoggerMiddleware асинхронным:

  1. Наследуйте django.utils.deprecation.MiddlewareMixin.
    • вызвать super().__init__(get_response) в __init__.
    • удалите __call__; MiddlewareMixin.__call__ сделает ваше промежуточное программное обеспечение асинхронным.
  2. Переформулируйте on_request в process_request.
    • вернуть None вместо common.
    • присоедините common к request вместо: request.common = common.
      не забудьте обновить ссылки на request.common.
    • присоедините request_time к request вместо self, чтобы сделать его (и промежуточное ПО) потокобезопасным.
      не забудьте обновить ссылки на request.request_time.
  3. Переформулируйте on_response(self, response, common) в process_response(self, request, response).
    • вернуть response.
    • не присоединяйте response_time к self; оставьте его в качестве переменной, поскольку он не используется в других функциях.

Результат:

Вернуться на верх