Tema 13
En microservicios, la forma en que los componentes se comunican define buena parte de su estabilidad y también de su seguridad operacional. Una llamada mal diseñada, un retry sin límites o una dependencia demasiado frágil pueden convertir una falla local en una degradación sistémica.
En un sistema distribuido cada llamada entre componentes es una posible fuente de latencia, error o saturación. A diferencia de una llamada interna en memoria, una interacción remota depende de la red, del estado del servicio remoto, de colas intermedias, de timeouts y de múltiples factores que pueden degradarse parcialmente.
Por eso la comunicación entre servicios no es solo un problema funcional. También es un problema de disponibilidad, contención de fallos y seguridad operacional. Un diseño ingenuo puede hacer que una dependencia lenta o abusada arrastre a todo el sistema.
En este tema analizaremos los tradeoffs entre comunicación síncrona y asíncrona, y veremos mecanismos como timeouts, retries controlados, circuit breakers y backpressure para reducir el impacto de fallos y comportamientos anómalos.
La comunicación síncrona ocurre cuando un componente realiza una llamada y espera una respuesta para continuar. Es común en APIs REST, gRPC o invocaciones RPC internas. Su ventaja principal es la simplicidad conceptual: el flujo es directo y la respuesta suele llegar en el mismo contexto de ejecución.
Pero esa simplicidad trae dependencia temporal. Si el servicio remoto tarda, falla o responde de manera intermitente, el componente llamador queda afectado inmediatamente.
La comunicación asíncrona desacopla temporalmente a emisor y receptor. El emisor publica un mensaje o evento y continúa sin esperar que el consumidor termine en ese mismo instante. Esto mejora desacople y tolerancia a ciertos picos o retrasos, pero introduce complejidad adicional en consistencia, observabilidad y manejo de errores.
La asincronía no elimina el problema de fallos; solo cambia su forma. Los errores pueden aparecer como retrasos, duplicaciones, reintentos, desorden de mensajes o acumulación en colas.
| Estilo | Ventaja principal | Riesgo principal |
|---|---|---|
| Síncrono | Flujo directo y respuesta inmediata | Acoplamiento temporal y cascadas de latencia |
| Asíncrono | Desacople y amortiguación temporal | Mayor complejidad de consistencia y trazabilidad |
No existe un estilo universalmente mejor. La elección depende de la necesidad funcional, la criticidad del flujo, la tolerancia al retraso y el costo de la inconsistencia temporal.
Uno de los rasgos más difíciles de los sistemas distribuidos es el fallo parcial. Un componente puede estar disponible para algunas solicitudes y no para otras, responder lentamente, rechazar parte del tráfico o degradarse sin caerse por completo. Esa ambigüedad hace más difícil decidir qué hacer desde el cliente.
Si varios servicios dependen en cadena entre sí, una degradación parcial puede propagarse. Un timeout en un servicio puede disparar retries, aumentar carga en el siguiente y generar una cascada que termine afectando todo el sistema.
Un timeout es el límite de tiempo tras el cual el llamador deja de esperar una respuesta. Parece un detalle técnico menor, pero en realidad es una decisión de arquitectura. Sin timeouts adecuados, los threads, conexiones o recursos del cliente pueden quedar bloqueados demasiado tiempo y amplificar una degradación remota.
Diseñar timeouts exige equilibrio:
Los retries ayudan a recuperarse de fallos transitorios, pero también pueden empeorar drásticamente una degradación. Si muchos clientes reintentan sin coordinación cuando un servicio ya está saturado, la carga aumenta justo cuando menos capacidad hay para absorberla.
Por eso los retries deberían ser:
Si una operación puede repetirse por retries o duplicación de mensajes, conviene preguntarse si es idempotente. Una operación idempotente permite múltiples intentos con el mismo efecto final esperado. Sin esta propiedad, los reintentos pueden generar cobros duplicados, actualizaciones inconsistentes o efectos irreversibles.
La idempotencia es una pieza clave de resiliencia y también una barrera frente a abuso accidental o malicioso de repeticiones.
El circuit breaker corta temporalmente el flujo hacia una dependencia que está fallando o degradada. En lugar de seguir enviando solicitudes que probablemente fallen, el sistema rechaza temprano o desvía a una estrategia degradada hasta que la dependencia pueda evaluarse nuevamente.
Su valor principal es evitar que el cliente desperdicie recursos y evitar que la dependencia colapsada siga recibiendo presión innecesaria.
| Estado | Qué ocurre | Objetivo |
|---|---|---|
| Cerrado | El tráfico circula normalmente | Operación estándar |
| Abierto | Se bloquean llamadas hacia la dependencia | Contener degradación |
| Half-open | Se prueba gradualmente si la dependencia se recuperó | Recuperación controlada |
Cuando un componente no puede procesar más trabajo al ritmo de llegada, necesita alguna forma de backpressure: señal o mecanismo que reduzca, rechace o regule la presión entrante. Sin esto, la cola interna crece, la latencia se dispara y la falla se propaga.
El backpressure puede expresarse en:
No todos los fallos requieren caída total. En muchos casos conviene diseñar degradación controlada: deshabilitar funciones no críticas, servir información cacheada, responder con un estado limitado o retrasar procesamiento no urgente.
Esta estrategia mejora continuidad operativa y también reduce el impacto de ataques de saturación o comportamientos anómalos temporales.
Aunque estos mecanismos suelen presentarse como temas de disponibilidad, también influyen en seguridad. Un sistema sin límites claros de tiempo y carga es más vulnerable a abuso, a denegaciones parciales y a efectos secundarios de componentes comprometidos o defectuosos.
Retries descontrolados, colas infinitas o timeouts mal elegidos pueden convertir un problema técnico menor en un incidente operativo serio.
No alcanza con implementar circuit breakers y timeouts. También hay que observarlos. Para eso conviene medir:
Sin esta visibilidad, la resiliencia queda configurada pero no gobernada.
Una arquitectura distribuida segura no solo protege identidad y acceso. También protege la capacidad del sistema para seguir operando cuando algo falla, se degrada o es abusado. Diseñar comunicación con límites claros, degradación controlada y observabilidad suficiente permite transformar fallos inevitables en incidentes contenidos en lugar de crisis sistémicas.
En el próximo tema estudiaremos seguridad en colas, eventos y arquitecturas basadas en mensajería para profundizar cómo proteger la comunicación asíncrona y sus riesgos particulares.