Marcio Cunha

Cómo blindar APIs REST contra abuso de peticiones con Redis

Aprende a proteger microservicios contra sobrecargas y ataques de denegación de servicio utilizando rate limiting distribuido con Redis y el algoritmo token bucket.

Marcio Cunha•7 min
También disponible en:EnglishPortuguês
Resumen
  • El algoritmo token bucket permite picos controlados de tráfico sin penalizar a usuarios legítimos.
  • Redis centraliza contadores en memoria con alta velocidad y soporte para operaciones atómicas.
  • Los microservicios distribuidos requieren sincronización eficiente para evitar falsos positivos.
  • Los scripts en Lua garantizan que las revisiones y actualizaciones del cubo ocurran sin concurrencia.
  • Las respuestas HTTP estandarizadas con cabeceras de límite ayudan a los clientes a gestionar colas.

Por qué las APIs modernas sufren de sobrecarga y abuso

Mantener una aplicación web en línea exige resiliencia contra picos inesperados de tráfico, scripts maliciosos y clientes que ejecutan docenas de llamadas por segundo. En la práctica, esto significa que un solo bot de rastreo o una integración mal configurada puede agotar los recursos de CPU y memoria de sus servidores backend. Para mitigar este riesgo de forma eficiente, la ingeniería de software recurre al rate limiting, que impone límites claros en la cantidad de solicitudes que cada usuario o dirección IP puede realizar en un intervalo específico. Si deseas profundizar tus defensas de infraestructura, vale la pena revisar cómo blindar APIs contra DoS usando rate limiting con Redis para garantizar estabilidad operacional bajo presión.

Cuando operamos en arquitecturas de microservicios, el desafío se vuelve aún más complejo porque las peticiones llegan dispersas a través de múltiples pasarelas de API y nodos de procesamiento. Si cada instancia decide contar los accesos de forma aislada en su propia memoria RAM, un cliente malintencionado multiplicará el límite de solicitudes por el número de servidores activos. Para resolver esta falla estructural, necesitamos una herramienta centralizada de alto rendimiento que almacene los contadores de forma compartida e instantánea. Es precisamente aquí donde entra el ecosistema distribuido, exigiendo una solución cohesiva y tolerante a fallos antes incluso de necesitar diagnosticar fallas profundas con herramientas como ¿microservicios fallando? cómo rastrear errores fácilmente en entornos de producción.

Cómo funciona el algoritmo token bucket en la práctica

Existen varias formas matemáticas de controlar el flujo de datos, pero el algoritmo conocido como token bucket, o cubo de tokens, destaca por su flexibilidad y elegancia. Imagina un cubo virtual que almacena fichas o tokens hasta un límite máximo preestablecido. Este cubo se reabastece constantemente con nuevos tokens a una tasa fija por segundo, por ejemplo, diez fichas nuevas cada diez segundos. Cada vez que una API recibe una solicitud HTTP, intenta retirar un token del cubo del cliente correspondiente. Si hay tokens disponibles, la solicitud avanza para su procesamiento y el cubo pierde un token; si el cubo está completamente vacío, la llamada se rechaza de inmediato con el código de estado HTTP 429 Too Many Requests.

La gran ventaja de este enfoque en comparación con los contadores simples de ventana fija es la autorización natural para ráfagas de tráfico legítimas, conocidas en el mercado como ráfagas o bursts. Si un usuario navega con normalidad y de repente carga una página llena de componentes que disparan múltiples llamadas simultáneas, consume los tokens acumulados en el cubo de una vez. Siempre que el volumen total no supere la capacidad máxima, la operación se completa con éxito. Poco después, el sistema repone los tokens consumidos gradualmente a medida que pasa el tiempo. Esto evita la frustración común en la que un usuario es bloqueado injustamente solo por realizar acciones rápidas y legítimas en el panel de la aplicación.

Implementando el control distribuido con Redis

Para llevar esta lógica a producción en un entorno con múltiples servidores, necesitamos una base de datos en memoria extremadamente rápida y confiable, siendo Redis la opción estándar de la industria. Redis almacena pares de clave y valor en la memoria principal, permitiendo consultas y actualizaciones en fracciones de milisegundo. Sin embargo, simplemente leer el conteo de tokens, hacer operaciones matemáticas en el lenguaje de nuestra API y guardarlo de vuelta puede generar un problema clásico de concurrencia llamado condición de carrera. Si dos solicitudes llegan exactamente en el mismo milisegundo, ambas podrían leer el mismo saldo antiguo y permitir el acceso indebido, superando el límite estipulado.

Para eliminar cualquier brecha de concurrencia sin perjudicar el rendimiento general del sistema, utilizamos scripts escritos en el lenguaje Lua ejecutados directamente dentro de Redis. El motor de ejecución de Redis garantiza que un script en Lua se ejecute de forma atómica, lo que significa que ninguna otra operación puede modificar esos datos mientras el script esté activo. El script calcula cuántos tokens se deben agregar desde la última verificación según el tiempo transcurrido, verifica si hay saldo suficiente, descuenta el token si es posible y devuelve el nuevo estado actualizado. De este modo, incluso si cientos de microservicios consultan el mismo Redis simultáneamente, la contabilidad permanece absolutamente precisa e incorruptible.

Estructurando el código del filtro de rate limiting

A continuación presentamos un ejemplo funcional en Node.js utilizando el cliente de Redis para aplicar la verificación del cubo de tokens antes de liberar la ruta de la API. El script en Lua encapsula toda la lógica matemática y de persistencia para garantizar atomicidad y máxima velocidad de respuesta en cada petición recibida.

const Redis = require('ioredis');const redis = new Redis();const tokenBucketScript = `local key = KEYS[1]local capacity = tonumber(ARGV[1])local refillRate = tonumber(ARGV[2])local now = tonumber(ARGV[3])local requested = tonumber(ARGV[4])local bucket = redis.call('HMGET', key, 'tokens', 'last_updated')local tokens = tonumber(bucket[1])local last_updated = tonumber(bucket[2])if not tokens then tokens = capacitylast_updated = nowelse local elapsed = math.max(0, now - last_updated) tokens = math.min(capacity, tokens + (elapsed * refillRate))endlocal allowed = 0if tokens >= requested then tokens = tokens - requested allowed = 1endredis.call('HMSET', key, 'tokens', tokens, 'last_updated', now)redis.call('EXPIRE', key, math.ceil(capacity / refillRate))return {allowed, tokens}`;async function checkRateLimit(identifier, capacity, refillRate, requested) { const now = Math.floor(Date.now() / 1000); const result = await redis.eval(tokenBucketScript, 1, `rate_limit:${identifier}`, capacity, refillRate, now, requested); return { allowed: result[0] === 1, remainingTokens: result[1] };}

Integrar este bloque de código en el middleware de tu aplicación web permite interceptar cualquier ruta crítica de forma transparente. Cuando la función devuelve que la solicitud no está permitida, el servidor interrumpe el flujo normal y devuelve una cabecera informativa al cliente. Es recomendable enviar cabeceras HTTP como X-RateLimit-Limit, X-RateLimit-Remaining y Retry-After para que el cliente sepa exactamente cuándo podrá realizar nuevos intentos sin generar frustración en la experiencia del usuario final.

Buenas prácticas operativas y consideraciones de resiliencia

Adoptar rate limiting distribuido con Redis requiere atención a detalles de infraestructura para evitar que el propio mecanismo de seguridad se convierta en un punto único de fallo en el sistema. Si el clúster de Redis sufre una caída repentina, toda tu API no debe dejar de funcionar simplemente porque no pudo validar los tokens. La práctica recomendada de ingeniería consiste en implementar un mecanismo de respaldo o tolerancia a fallos en el código del middleware. En caso de un error de conexión con Redis, la aplicación debe registrar la alerta y permitir temporalmente el paso de la solicitud, priorizando la disponibilidad del servicio por encima del control estricto de tráfico.

Otro punto crítico de diseño implica elegir correctamente la clave utilizada para identificar al cliente en Redis. Depender únicamente de la dirección IP de la máquina solicitante puede causar bloqueos injustos en redes corporativas, oficinas o universidades donde cientos de personas comparten la misma IP pública de salida. Para evitar este tipo de colisión operacional, el enfoque ideal es combinar la dirección IP con identificadores contextuales adicionales, como el token de autenticación del usuario conectado o la clave de API suministrada en la cabecera de la solicitud. De este modo, el control de abuso se vuelve quirúrgico, afectando únicamente el comportamiento individual y anómalo sin perjudicar a usuarios legítimos que comparten la misma infraestructura de red. Para profundizar en estos conceptos arquitectónicos en producción, consulte también nuestro análisis técnico sobre ¿Por qué tu app de React se traba? Cómo usar React Profiler.

Consideraciones Finales

Blindar APIs REST contra el abuso de solicitudes dejó de ser un lujo operativo y se ha convertido en un requisito básico de supervivencia para cualquier arquitectura moderna de microservicios. La combinación del algoritmo token bucket con la velocidad y atomicidad de Redis ofrece un equilibrio perfecto entre flexibilidad para picos de tráfico legítimos y protección rigurosa contra sobrecargas maliciosas. Al implementar scripts en Lua y garantizar estrategias inteligentes de direccionamiento por claves y respaldo, tu equipo de ingeniería construye un ecosistema robusto capaz de absorber impactos y mantener la estabilidad operacional incluso bajo condiciones extremas de uso en el mundo real.

Invertir tiempo en la planificación de estas capas de protección previene interrupciones costosas y mejora significativamente la reputación y confiabilidad de tus servicios ante los clientes. Recuerda monitorear continuamente las métricas de bloqueo y ajustar los límites del cubo de acuerdo con el comportamiento real de tu audiencia, asegurando una evolución constante y segura de tu infraestructura tecnológica.