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