La limitación de tasas (Rate Limiting) es una característica fundamental para proteger los sistemas, mantener la calidad del servicio y gestionar el acceso a los recursos de manera eficiente. Esta herramienta se utiliza principalmente para prevenir ataques de denegación de servicio (DoS) al limitar la cantidad de solicitudes que un usuario o aplicación puede enviar en un período de tiempo determinado. Además, ofrece una forma de asegurar que todos los usuarios tengan acceso equitativo a los servicios sin que la sobrecarga de solicitudes afecte el rendimiento general del sistema.
Existen diferentes enfoques para implementar la limitación de tasas en una API REST de ASP.NET Core 8, y cada uno de ellos tiene ventajas y limitaciones según las necesidades específicas del proyecto.
Uno de los modelos más comunes es el Fixed Window. Este modelo limita el número de solicitudes autorizadas dentro de una ventana de tiempo específica. Por ejemplo, un usuario puede enviar hasta 50 solicitudes en 15 segundos. Una vez que el límite se alcanza, las solicitudes adicionales se rechazan, pero se pueden encolar hasta que la ventana de tiempo se reinicie. Este tipo de limitación es eficaz para controlar el tráfico y evitar la sobrecarga, pero no ofrece una flexibilidad completa frente a las solicitudes que se realizan justo después de que el contador se reinicia.
El Sliding Window es otro modelo interesante que funciona de manera similar al Fixed Window, pero con una diferencia clave. Este modelo divide la ventana de tiempo en segmentos y tras cada período, transfiere el número de solicitudes permitidas al siguiente segmento, lo que permite un control más dinámico del flujo de solicitudes. A pesar de que la cantidad total de solicitudes permitidas no cambia, la distribución de los límites entre los segmentos es más fluida, lo que reduce la posibilidad de que se rechacen solicitudes de manera abrupta al final de un ciclo.
El modelo de Token Bucket introduce un concepto de "tokens" que se almacenan en un "balde". Cada vez que se autoriza una solicitud, se utiliza un token. Si no hay tokens disponibles, las solicitudes se rechazan automáticamente. Sin embargo, a diferencia del modelo Fixed Window, los tokens se recargan gradualmente dentro de un período determinado. Esto proporciona una forma más constante y controlada de gestionar el acceso, permitiendo un número limitado de solicitudes en cualquier momento, pero con la posibilidad de recargar los tokens para permitir un mayor flujo de tráfico sin desbordar el sistema.
El modelo de Concurrency es el más sencillo y se basa en limitar la cantidad de solicitudes simultáneas permitidas en un sistema. Es decir, permite un número específico de solicitudes concurrentes antes de rechazar nuevas peticiones. Este modelo es útil cuando se necesitan garantizar tiempos de respuesta consistentes, aunque no gestiona la cantidad total de solicitudes en un período dado, sino solo las que están siendo procesadas al mismo tiempo.
Es importante entender que todos estos modelos permiten la configuración de un "partition key", que puede ser cualquier tipo de identificador único como el ID de usuario o la dirección IP. Si no se define un partition key, la limitación se aplica de manera global, lo que significa que el límite de solicitudes se aplica a todos los usuarios por igual.
Cuando una solicitud se rechaza debido a la limitación de tasa, ASP.NET Core 8 devuelve por defecto un código de estado 503, que es incorrecto. Lo adecuado sería utilizar el código de estado HTTP 429: Too Many Requests, ya que indica que el cliente ha superado los límites permitidos y debe intentar la solicitud más tarde.
Para implementar la limitación de tasas en una aplicación de ASP.NET Core 8, se debe utilizar el método de extensión AddRateLimiter para definir las reglas de limitación y el middleware UseRateLimiter para habilitar estas reglas dentro de la tubería de procesamiento de la aplicación. El siguiente fragmento de código muestra cómo se puede definir un limitador basado en el modelo Fixed Window con una capacidad de encolado y un límite de solicitudes:
En este ejemplo, el límite de 50 solicitudes se establece para una ventana de 15 segundos. Si se alcanzan los 50 permisos, las solicitudes adicionales se encolan hasta que el contador se reinicie. Además, se devuelve un mensaje personalizado cuando se rechaza una solicitud debido a la limitación de tasas.
ASP.NET Core 8 también permite deshabilitar la limitación de tasas en puntos finales específicos mediante el uso de la extensión DisableRateLimiting. Esto es útil en situaciones en las que ciertos servicios o endpoints deben estar exentos de la limitación de tasas, como cuando se trata de un servicio de autenticación o un endpoint de administración:
Por último, se pueden definir múltiples políticas de limitación de tasas, las cuales se aplican de manera selectiva a diferentes endpoints mediante la extensión RequireRateLimiting. Esto permite tener reglas más detalladas y personalizadas para diferentes rutas o funcionalidades dentro de la misma aplicación, optimizando el uso de
¿Cómo implementar un limitador de solicitudes dinámico dependiendo del plan de precios del cliente?
El concepto de limitación de solicitudes es fundamental cuando se busca controlar el tráfico hacia un servidor, especialmente en servicios con diferentes tipos de usuarios, como en un sistema de suscripción con planes gratuitos y de pago. Al definir un limitador de tasa global junto con limitadores específicos según políticas definidas, se puede gestionar de manera eficiente el uso de recursos sin sacrificar la experiencia del usuario.
El principio de la limitación de solicitudes (rate limiting) no es un concepto nuevo, pero la flexibilidad en su implementación sí lo es. Imagina un servicio web que devuelve un "plan de precios" basado en la dirección IP del cliente. De acuerdo con este plan, el servicio podrá aplicar diferentes reglas para limitar la cantidad de solicitudes realizadas por cada cliente en función de su plan de precios.
En un escenario de ejemplo, tenemos un servicio IPricingTierService que devuelve un enumerado PricingTier dependiendo de la IP del cliente. Esto nos permite asignar a cada usuario una política de limitación diferente según si están en un plan gratuito o de pago.
El servicio IPricingTierService se implementa de la siguiente manera, como se puede ver en el siguiente código:
El enumerado PricingTier tiene dos valores, Free y Paid, donde cada uno corresponde a un tipo de usuario con permisos de acceso y cantidad de solicitudes diferentes:
Para registrar este servicio, simplemente vinculamos su implementación en el contenedor de dependencias:
Una vez registrado, podemos acceder al servicio desde el contexto HTTP utilizando la inyección de dependencias a través de httpContext.RequestServices.GetRequiredService<IPricingTierService>(). De esta forma, cuando un usuario realice una solicitud, el sistema podrá identificar su dirección IP y asignarle el plan correspondiente, ya sea gratuito o de pago.
Luego, configuramos el limitador global de tasa utilizando la política de limitación que depende de la IP del cliente. Para implementar este mecanismo, usamos el siguiente código:
En este ejemplo, el límite de solicitudes para los usuarios con un plan gratuito se fija en 1 solicitud por cada 15 segundos, mientras que los usuarios de pago tienen una mayor capacidad, permitiéndoles realizar hasta 50 solicitudes en el mismo período. Es importante destacar que se está utilizando el modelo de "ventana fija" (Fixed Window), que es ideal para este tipo de situaciones donde se quiere controlar estrictamente el acceso durante intervalos de tiempo fijos.
Modelos de limitación de solicitudes
A lo largo del desarrollo de sistemas de este tipo, se presentan varias estrategias para implementar la limitación de solicitudes. Cada una de ellas tiene ventajas y desventajas, dependiendo del tipo de tráfico y de las necesidades específicas de los usuarios.
-
Modelo de ventana deslizante (Sliding Window): En este modelo, se define el número de segmentos que compartirán el número limitado de solicitudes y la ventana de tiempo. Este modelo es más flexible que el de ventana fija, ya que permite un control más dinámico de las solicitudes a medida que van pasando.
-
Modelo de cubo de tokens (Token Bucket): Este modelo es más agresivo en comparación con los anteriores. En este caso, el número de "tokens" disponibles define el límite de solicitudes. Los tokens se reponen a intervalos regulares, y si un cliente no tiene tokens disponibles, sus solicitudes serán rechazadas inmediatamente. Este modelo es adecuado cuando se desea permitir un comportamiento más flexible con respecto a la tasa de solicitudes, pero sin que se acumulen muchas solicitudes pendientes.
-
Modelo de concurrencia: Este es otro enfoque interesante donde las solicitudes se limitan en función de la cantidad de solicitudes concurrentes que un cliente puede hacer en un determinado período de tiempo.
Cada uno de estos modelos puede ser implementado de la misma manera que el modelo de ventana fija, solo que con algunas diferencias en las clases utilizadas y los parámetros de configuración. Por ejemplo, para el modelo de cubo de tokens, el código se vería de la siguiente forma:
Este modelo es más agresivo porque no permite que las solicitudes se acumulen en una cola. Si no hay tokens disponibles, la solicitud es rechazada de inmediato, lo que puede resultar en una experiencia de usuario menos tolerante en algunos escenarios.
Consideraciones importantes
Cuando implementamos cualquier tipo de limitador de tasa, es crucial entender que la elección del modelo dependerá de varios factores, como el tipo de usuarios, la arquitectura de la aplicación y el comportamiento esperado del tráfico. Aunque la flexibilidad es una ventaja, también hay que tener en cuenta que una implementación incorrecta puede llevar a una mala experiencia de usuario, especialmente si las reglas de limitación son demasiado estrictas o inapropiadas para ciertos usuarios. Además, las decisiones sobre qué modelo de limitación usar deben considerar el impacto en el rendimiento y en la escalabilidad del sistema.

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский