pregunta en resumen
Migré mi proyecto de Django 2.2 a Django 3.2, y ahora quiero comenzar a usar la posibilidad de vistas asíncronas. Creé una vista asíncrona, configuré la configuración asgi y ejecuté gunicorn con un trabajador de Uvicorn. Al llenar este servidor con 10 usuarios al mismo tiempo, se sirven de forma sincrónica. ¿Qué necesito configurar para servir a 10 usuarios simultáneos una vista asíncrona?
pregunta en detalle
Esto es lo que hice hasta ahora en mi entorno local:
gunicorn y uvicorn a través de pipasgi.py con los siguientes contenidos 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 MyService.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.Esto último no es lo que esperaba. Espero que el trabajador, mientras duerme de forma asíncrona, ya recoja la siguiente solicitud. ¿Me estoy perdiendo alguna configuración?
También lo probé usando Daphne en lugar de Uvicorn, pero con el mismo resultado.
Langosta
Así es como he puesto mi langosta.
pip install locustlocustfile.py con el siguiente contenido: from locust import HttpUser, task class SleepUser(HttpUser): @task def async_sleep(self): self.client.get('/api/async_sleep/')software intermedio
Estas son mis configuraciones de middleware
MIDDLEWARE = [ 'django_prometheus.middleware.PrometheusBeforeMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.gzip.GZipMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', 'shared.common.middleware.ApiLoggerMiddleware', 'django_prometheus.middleware.PrometheusAfterMiddleware', ]El ApiLoggerMiddleware de shared es de nuestro propio código, primero investigaré este. Esta es la implementación de la misma.
import logging import os from typing import List from django.http import HttpRequest, HttpResponse from django.utils import timezone from shared.common.authentication_service import BaseAuthenticationService class ApiLoggerMiddleware: TOO_BIG_FOR_LOG_BYTES = 2 * 1024 def __init__(self, get_response): # The get_response callable is provided by Django, it is a function # that takes a request and returns a response. Plainly put, once we're # done with the incoming request, we need to pass it along to get the # response which we need to ultimately return. self._get_response = get_response self.logger = logging.getLogger('api') self.pid = os.getpid() self.request_time = None self.response_time = None def __call__(self, request: HttpRequest) -> HttpResponse: common_data = self.on_request(request) response = self._get_response(request) self.on_response(response, common_data) return response def truncate_body(self, request: HttpRequest) -> str: return f"{request.body[:self.TOO_BIG_FOR_LOG_BYTES]}" def on_request(self, request: HttpRequest) -> List[str]: self.request_time = timezone.now() remote_address = self.get_remote_address(request) user_agent = request.headers.get('User-Agent') or '' customer_uuid = self.get_customer_from_request_auth(request) method = request.method uri = request.get_raw_uri() common = [ remote_address, user_agent, customer_uuid, method, uri ] in_line = [ "IN", str(self.pid), str(self.request_time), ] + common + [ self.truncate_body(request) ] self.logger.info(', '.join(in_line)) return common def on_response(self, response: HttpResponse, common: List[str]) -> None: self.response_time = timezone.now() out_line = [ "OUT", str(self.pid), str(self.response_time) ] + common + [ str(self.response_time - self.request_time), str(response.status_code), ] self.logger.info(", ".join(out_line)) @classmethod def get_customer_from_request_auth(cls, request: HttpRequest) -> str: token = request.headers.get('Authorization') if not token: return 'no token' try: payload = BaseAuthenticationService.validate_access_token(token) return payload.get('amsOrganizationId', '') except Exception: return 'unknown' @classmethod def get_remote_address(cls, request: HttpRequest) -> str: if 'X-Forwarded-For' in request.headers: # in case the request comes in through a proxy, the remote address # will be just the last proxy that passed it along, that's why we # have to get the remote from X-Forwarded-For # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For addresses = request.headers['X-Forwarded-For'].split(',') client = addresses[0] return client else: return request.META.get('REMOTE_ADDR', '')Fuentes
Fuentes que he usado:
Al ejecutar el comando gunicorn , puede intentar agregar el parámetro de workers usando las opciones -w o --workers .
El valor predeterminado es 1 , como se indica en la documentación de gunicorn . Es posible que desee intentar aumentar ese valor.
Ejemplo de uso:
gunicorn MyService.asgi:application -k uvicorn.workers.UvicornWorker -w 10 Además, es posible que deba verificar otros atributos de trabajadores en la documentación, como worker_class y threads .