Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Controle el ritmo al que su aplicación envía solicitudes a un servicio para mantenerse dentro de los límites de limitación de solicitudes del servicio y de su capacidad total. Este enfoque le ayuda a evitar o minimizar los errores de limitación y predecir con mayor precisión el rendimiento.
La limitación de velocidad es adecuada en muchos escenarios, pero resulta especialmente útil para tareas automatizadas repetitivas a gran escala, como el procesamiento por lotes.
Contexto y problema
Realizar un gran número de operaciones en un servicio limitado puede dar lugar a un aumento del tráfico y a reducir el rendimiento, ya que necesita realizar un seguimiento de las solicitudes rechazadas y, a continuación, volver a intentar las operaciones. A medida que aumenta el número de operaciones, un límite de limitación puede requerir varios pasos de reenvío de datos, lo que da lugar a un mayor impacto en el rendimiento.
Por ejemplo, considere el siguiente proceso problemático de reintento en error para ingerir datos en Azure Cosmos DB:
La aplicación debe ingerir 10 000 registros en Azure Cosmos DB. Cada registro cuesta 10 unidades de solicitud (RU) para ingerir, por lo que se requiere un total de 100 000 RU para completar el trabajo.
La instancia de Azure Cosmos DB tiene 20 000 RU de capacidad aprovisionada.
Envía los 10 000 registros a Azure Cosmos DB. 2000 registros se escriben correctamente y 8000 registros se rechazan.
Los 8000 registros restantes se envían a Azure Cosmos DB. 2000 registros se escriben correctamente y 6000 registros se rechazan.
Los 6000 registros restantes se envían a Azure Cosmos DB. 2000 registros se escriben correctamente y 4000 registros se rechazan.
Los 4000 registros restantes se envían a Azure Cosmos DB. 2000 registros se escriben correctamente y 2000 registros se rechazan.
Se envían los 2000 registros restantes a Azure Cosmos DB. Todos los registros se han escrito correctamente.
El trabajo de ingesta se completa correctamente, pero solo después de enviar 30 000 registros a Azure Cosmos DB. Todo el conjunto de datos consta de solo 10 000 registros.
Hay otros factores que se deben tener en cuenta en este ejemplo:
Un gran número de errores también puede dar lugar a un trabajo adicional para registrar estos errores y procesar los datos de registro resultantes. El enfoque anterior controla 20 000 errores y el registro de estos errores podría imponer un costo de procesamiento, memoria o recurso de almacenamiento.
Dado que no conoce los límites del servicio de ingesta, no tiene una manera de establecer expectativas durante cuánto tiempo tarda el procesamiento de datos. La limitación de velocidad puede permitirle calcular el tiempo necesario para la ingesta.
Solución
La limitación de velocidad puede reducir el tráfico y mejorar potencialmente el rendimiento al reducir el número de registros enviados a un servicio durante un período de tiempo determinado.
Un servicio puede limitar las solicitudes en función de diferentes métricas a lo largo del tiempo, como:
- Número de operaciones (por ejemplo, 20 solicitudes por segundo).
- Cantidad de datos (por ejemplo, 2 GiB por minuto).
- Costo relativo de las operaciones (por ejemplo, 20 000 RU por segundo).
Independientemente de la métrica que use para la limitación, la implementación de limitación de velocidad implica controlar el número o el tamaño de las operaciones enviadas al servicio durante un período de tiempo específico. La limitación de velocidad optimiza el uso del servicio sin superar su capacidad de limitación.
En escenarios en los que su API puede manejar las solicitudes más rápido de lo que permiten los servicios de ingesta con limitación de velocidad, debe gestionar la velocidad a la que utiliza el servicio. Tratar la limitación solo como un desajuste de tasa de datos y almacenar en búfer las solicitudes de ingesta hasta que el servicio se recupere genera riesgos. Si la aplicación deja de responder en este escenario, es posible que se pierdan los datos almacenados en búfer.
Para evitar este riesgo, considere la posibilidad de enviar los registros a un sistema de mensajería duradero que pueda controlar la velocidad de ingesta total. (Los servicios como Azure Event Hubs pueden controlar millones de operaciones por segundo). A continuación, puede usar uno o varios procesadores de trabajos para leer los registros del sistema de mensajería a una velocidad controlada que se encuentre dentro de los límites del servicio limitado. El envío de registros al sistema de mensajería puede ahorrar memoria interna al permitir extraer de la cola solo los registros que se pueden procesar en un intervalo de tiempo determinado.
Azure proporciona varios servicios de mensajería duraderos que puede usar con este patrón, entre los que se incluyen:
Al enviar registros, es posible que el período de tiempo que use para liberar registros sea más granular que el período en el que se limita el servicio. Los sistemas suelen establecer limitaciones basadas en intervalos de tiempo con los que se puede comprender y trabajar fácilmente. Sin embargo, para el equipo que ejecuta un servicio, estos períodos de tiempo pueden ser muy largos en comparación con la rapidez con la que puede procesar información. Por ejemplo, un sistema podría aplicar una limitación por segundo o por minuto, pero normalmente el código se ejecuta en escalas del orden de nanosegundos o milisegundos.
Aunque no es necesario, a menudo se recomienda enviar un número menor de registros con más frecuencia para mejorar el rendimiento. Por lo tanto, en lugar de intentar procesar por lotes los registros de una versión una vez por segundo o una vez por minuto, puede ser más granular que para mantener el consumo de recursos (memoria, CPU y red) fluyendo a una velocidad más uniforme. Este enfoque evita posibles cuellos de botella causados por ráfagas repentinas de solicitudes. Por ejemplo, si un servicio permite 100 operaciones por segundo, la implementación de un limitador de velocidad podría incluso agotar las solicitudes liberando 20 operaciones cada 200 milisegundos, como se muestra en el gráfico siguiente.
Además, a veces es necesario que varios procesos no coordinados compartan un servicio con limitaciones. Para implementar la limitación de velocidad en este escenario, puede particionar lógicamente la capacidad del servicio y, a continuación, usar un sistema de exclusión mutua distribuida para administrar bloqueos exclusivos en esas particiones. Los procesos no coordinados podrán entonces competir por los bloqueos en esas particiones siempre que necesiten capacidad. Por cada partición sobre la que un proceso mantiene un bloqueo, se le asigna una cierta cantidad de capacidad.
Por ejemplo, si el sistema limitado permite 500 solicitudes por segundo, puede crear 20 particiones de 25 solicitudes por segundo cada una. Si un proceso necesitara generar 100 solicitudes, podría solicitar cuatro particiones al sistema de exclusión mutua distribuido. El sistema podría conceder dos particiones durante 10 segundos. Luego, el proceso limitaría la velocidad a 50 solicitudes por segundo, completaría la tarea en 2 segundos y liberaría el bloqueo.
Una manera de implementar este patrón es usar Azure Storage. En este escenario, creará un blob de 0 bytes por cada partición lógica en un contenedor. Entonces, las aplicaciones pueden obtener arrendamientos exclusivos directamente sobre esos blobs por un breve período de tiempo (por ejemplo, 15 segundos). Para cada concesión que se concede a una aplicación, puede usar la cantidad de capacidad de esa partición. A continuación, la aplicación debe realizar un seguimiento del tiempo de concesión para que, cuando expire el tiempo, la aplicación puede dejar de usar la capacidad a la que se concedió. Al implementar este patrón, a menudo querrá que cada proceso intente conceder una partición aleatoria cuando necesite capacidad.
Para reducir aún más la latencia, puede asignar una pequeña cantidad de capacidad exclusiva para cada proceso. De ese modo, un proceso solo buscaría obtener una concesión de capacidad compartida si fuera necesario superar su capacidad reservada.
Como alternativa a Azure Storage, también puede implementar este tipo de sistema de administración de concesiones mediante tecnologías como ZooKeeper, etcd y Redis/Redsync.
Problemas y consideraciones
Tenga en cuenta los siguientes puntos a medida que decida cómo implementar este patrón:
Aunque el patrón de limitación de velocidad puede reducir el número de errores de limitación, la aplicación todavía necesita controlar correctamente los errores de limitación que puedan producirse.
Asegúrese de que los reintentos se coordinan con la limitación de velocidad. Los reintentos ciegos o demasiado agresivos pueden aumentar la carga y crear tormentas de reintento, por lo que propaga las señales de presión inversa (por ejemplo, HTTP 429 con
Retry-After) y usan un número limitado de reintentos con pequeños retrasos aleatorios entre intentos.Si la aplicación tiene varias secuencias de trabajo que acceden al mismo servicio limitado, debe integrarlas todas en la estrategia de limitación de velocidad. Por ejemplo, puede admitir la carga masiva de registros en una base de datos, pero también consultar registros en esa misma base de datos. Puede administrar la capacidad asegurándose de que todas las secuencias de trabajo están cerradas a través del mismo mecanismo de limitación de velocidad. Como alternativa, puede reservar grupos de capacidad independientes para cada flujo de trabajo.
El servicio regulado puede usarse en varias aplicaciones. En algunos casos, es posible coordinar ese uso (como se muestra anteriormente en este artículo). Si empieza a ver un número mayor de lo esperado de errores de limitación, ese mayor número podría indicar la contención entre las aplicaciones que acceden a un servicio. En este caso, es posible que tenga que considerar la posibilidad de reducir temporalmente el rendimiento impuesto por el mecanismo de limitación de velocidad hasta que se reduzca el uso de otras aplicaciones.
Cuándo usar este patrón
Use este patrón cuando:
Debe reducir los errores de limitación generados por un servicio de velocidad limitado.
Quiere minimizar el tráfico, en comparación con los enfoques naïve retry-on-error.
Debe reducir el consumo de memoria mediante la puesta en cola de registros solo cuando haya suficiente capacidad para procesarlos.
Este patrón podría no ser adecuado cuando:
La operación requiere una finalización inmediata y sincrónica con una latencia muy baja y no puede tolerar colas ni procesamiento diferido.
El cuello de botella principal no es la tasa de solicitudes, pero en su lugar es la simultaneidad o contención de recursos (por ejemplo, saturación de CPU o trabajo en curso de larga duración). En estos casos, los controles de escalado o simultaneidad son más adecuados.
Diseño de cargas de trabajo
Evalúe cómo usar el patrón de limitación de velocidad en el diseño de una carga de trabajo para abordar los objetivos y principios descritos en los pilares de Azure Well-Architected Framework. En la tabla siguiente se proporciona una guía sobre cómo este patrón apoya los objetivos de cada pilar.
| Fundamento | Cómo apoya este patrón los objetivos de los pilares |
|---|---|
| Las decisiones de diseño de fiabilidad ayudan a que su carga de trabajo sea resiliente a fallos y garantizan que se recupere a un estado de pleno funcionamiento después de que se produzca un fallo. | Esta táctica protege al cliente al reconocer y respetar las limitaciones y los costos de comunicarse con un servicio cuando el servicio prefiere evitar un uso excesivo. - RE:07 Autopreservación |
Si este patrón introduce concesiones dentro de un pilar, considérelas en relación con los objetivos de los otros pilares.
Example
La siguiente aplicación de ejemplo permite a los usuarios enviar registros de varios tipos a una API. Cada tipo de registro tiene un procesador de trabajos único que realiza los pasos siguientes:
- Validation
- Enriquecimiento
- Inserción del registro en la base de datos
Todos los componentes de la aplicación (API, procesador de trabajos A y procesador de trabajos B) son procesos independientes que se pueden escalar de forma independiente. Los procesos no se comunican directamente entre sí.
En este ejemplo, cada concesión de blobs representa un recurso compartido fijo del rendimiento de la base de datos permitido. Un procesador solo puede anular la cola y escribir a la velocidad combinada de las concesiones que contiene actualmente. A medida que los procesadores obtienen o pierden concesiones a lo largo del tiempo, su velocidad de escritura permitida cambia, lo que mantiene el tráfico total de la base de datos dentro del límite configurado, a la vez que deja que todo el trabajo en cola avance.
Este diagrama incorpora el siguiente flujo de trabajo:
- Un usuario envía 10 000 registros de tipo A a la API.
- La API pone en cola esos 10 000 registros en la cola A.
- Un usuario envía 5000 registros de tipo B a la API.
- La API pone en cola esos 5000 registros en la cola B.
- Procesador de trabajos A ve que la cola A tiene registros e intenta obtener una concesión exclusiva en el blob 2.
- El procesador de trabajos B ve que la cola B tiene registros e intenta obtener una concesión exclusiva en el blob 2.
- El procesador de trabajos A no puede obtener la concesión.
- El procesador de trabajos B obtiene la concesión del blob 2 durante 15 segundos. Ahora puede limitar la velocidad de las solicitudes a la base de datos a una velocidad de 100 por segundo.
- El procesador de trabajos B quita 100 registros de la cola B y los escribe.
- Transcurre un segundo.
- Procesador de trabajos A ve que la cola A tiene más registros e intenta obtener una concesión exclusiva en el blob 6.
- El procesador de trabajos B ve que la cola B tiene más registros e intenta obtener una concesión exclusiva en el blob 3.
- Procesador de trabajos A obtiene la concesión del blob 6 durante 15 segundos. Ahora puede limitar la velocidad de las solicitudes a la base de datos a una velocidad de 100 por segundo.
- El procesador de trabajos B obtiene la concesión del blob 3 durante 15 segundos. Ahora puede limitar la velocidad de las solicitudes a la base de datos a una velocidad de 200 por segundo. (También mantiene la concesión del blob 2).
- Procesador de trabajos: pone en cola 100 registros de la cola A y los escribe.
- El procesador de trabajos B quita 200 registros de la cola B y los escribe.
- Transcurre un segundo.
- Procesador de trabajos A ve que la cola A tiene más registros e intenta obtener una concesión exclusiva en el blob 0.
- El procesador de trabajos B ve que la cola B tiene más registros e intenta obtener una concesión exclusiva en el blob 1.
- Procesador de trabajos A obtiene la concesión del blob 0 durante 15 segundos. Ahora puede limitar la velocidad de las solicitudes a la base de datos a una velocidad de 200 por segundo. (También mantiene la concesión del blob 6).
- El procesador de trabajos B obtiene la concesión del blob 1 durante 15 segundos. Ahora puede limitar la velocidad de las solicitudes a la base de datos a una velocidad de 300 por segundo. (También mantiene la concesión de los blobs 2 y 3).
- Procesador de trabajos: pone en cola 200 registros de la cola A y los escribe.
- El procesador de trabajos B quita 300 registros de la cola B y los escribe.
- Y así sucesivamente.
Después de 15 segundos, uno o ambos trabajos aún no se completarán. A medida que expiran las concesiones, un procesador también debe reducir el número de solicitudes que desqueue y escriba.
Las implementaciones de este patrón están disponibles en diferentes lenguajes de programación:
- La implementación de Go está disponible en GitHub.
- La implementación de Java está disponible en GitHub.
Pasos siguientes
Las instrucciones siguientes también pueden ser relevantes al implementar este patrón:
Limitación avanzada de solicitudes con Azure API Management. Úselo como control de admisión perimetral complementario para aplicar límites y cuotas de frecuencia de llamadas por clave, y devolver señales de presión inversa coherentes a los clientes.
Elegir entre los servicios de mensajería de Azure. Seleccione la mejor red troncal de mensajería duradera para almacenar en búfer y controlar la ingesta.
Controle los errores transitorios en las aplicaciones de Azure. Diseñe el comportamiento de reintento para que los clientes se desactiven correctamente cuando se alcancen los límites.
Recursos relacionados
Los siguientes patrones e instrucciones también pueden ser relevantes al implementar este patrón:
Throttling. Normalmente, el patrón de limitación de velocidad se implementa en respuesta a un servicio limitado.
Retry. Cuando las solicitudes a un servicio limitado producen errores de limitación, generalmente es adecuado volver a intentar esas solicitudes después de un intervalo adecuado.
Queue-Based nivelación de carga es similar al patrón de limitación de velocidad, pero difiere de varias maneras clave:
La limitación de velocidad no necesita usar colas para administrar la carga, pero sí que debe usar un servicio de mensajería duradero. Por ejemplo, un patrón de limitación de velocidad puede usar servicios como Apache Kafka o Event Hubs.
El patrón de limitación de velocidad presenta el concepto de un sistema de exclusión mutua distribuida en particiones, lo que permite administrar la capacidad de varios procesos no coordinados que se comunican con el mismo servicio limitado.
Un Queue-Based patrón de nivelación de carga es aplicable siempre que haya una discrepancia de rendimiento entre los servicios o quiera mejorar la resistencia. Por lo tanto, es un patrón más amplio que la limitación de velocidad, que se preocupa más específicamente por el acceso eficaz a un servicio limitado.