WebSockets и SSE
1) Corto: qué y para qué
WebSocket (WS/WSS): actualiza la conexión HTTP a un canal dúplex completo. Adecuado para chats, juegos en vivo, colaboración, telemetría bidireccional.
Eventos Server-Sent (SSE): flujo de un solo lado del servidor al navegador (MIME 'text/event-stream'). Ideal para tickers, notificaciones, cotizaciones, progreso de tareas. El cliente es 'EventSource'.
- Necesita una entrada del cliente en tiempo real (a menudo y mucho) → WebSocket.
- Sólo las actualizaciones push del servidor, la compatibilidad y la simplicidad son más importantes → SSE.
2) Red y protocolos
2. 1 Transporte e interoperabilidad
WebSocket: comienza como HTTP 'GET... Upgrade: websocket` (HTTP/1. 1). Para HTTP/2 es posible RFC 8441 (CONNECT + ': protocol = websocket'), el soporte depende del proxy. Funciona en la parte superior de TLS (WSS) - necesariamente en la venta.
SSE: respuesta HTTP de larga duración normal ('200 OK') con streaming. Va muy bien a través de la HTTP/1. 1/2/3, compatible con CDN/proxy (si no cortan los conectores largos).
2. 2 Proxy/balanceadores/CDN
Compruebe: compatibilidad con conexiones de larga vida, idla y timauts, sesiones sticky (si el estado está en el nodo).
Para WS: habilite 'proxy _ read _ timeout', 'upgrade' -máquinas, límite de conexión PER.
Para SSE: asegúrese de que el proxy no guarde la respuesta (de lo contrario, el cliente no verá los eventos a tiempo).
3) Modelo de mensajes y control de flujo
WebSocket: fotogramas de texto o binarios; hay 'ping/pong', pero no hay backpressure integrado - implemente en la aplicación (colas, ventanas, drop-policy).
SSE: eventos de texto (UTF-8); el cliente en línea sabe reconnect con retraso; el servidor puede especificar 'retry:'. Hay un 'id:' y un título 'Last-Event-ID' para reanudar desde la posición deseada.
- Cubra los mensajes salientes por cliente.
- Limite la cola de eventos no enviados; en caso de desbordamiento: deseche los agregados de baja prioridad.
- Para WS: utilice la «ventana deslizante» y el diagrama ACK a nivel de aplicación.
4) Fiabilidad de las conexiones
4. 1 Detección y keepalive
WS: enviar 'ping' cada N segundos; la brecha de temporizador es reconnect con backoff + jitter exponencial.
SSE: el servidor hace «comentarios» :\n 'como heartbeat para que la conexión no cuente idle; el cliente se reconectará.
4. 2 Recuperación de flujo
WS: mantenga mensajes offset/sequence y solicite delta después de reconnect.
SSE: use 'id:' para cada evento y 'Last-Event-ID' en la solicitud - el servidor comprime los eventos perdidos.
5) Autenticación y autorización
El JWT Bearer en la URL de solicitud (WS) no es seguro (fugas en los logs). Utilice el encabezado (a través del handshake HTTP primario) o las cookies con las marcas 'Secure', 'HttpOnly', 'SameSite'.
mTLS es posible (especialmente para B2B), así como la firma (HMAC) encima de la solicitud original.
Para SSE con cookies, recuerda sobre CORS ('Access-Control-Allow-Origin', 'Allow-Credentials').
Rotación de token: no corte el stream. Transfiera «pronto expirará» → el cliente volverá a abrir la conexión con el nuevo token.
6) Formato de datos y compresión
WebSocket: habilite permessage-deflate cuidadosamente (CPU); evitar la compresión de formatos ya comprimidos (Proto, Avro). El payload binario es más económico que el JSON.
SSE: este es el texto; para los datos de gran tamaño, envíe un enlace a un recurso NAT/gRPC o archivos chunk; gzip de transporte para SSE - adecuado, pero tenga en cuenta la amortiguación en proxy/CDN.
7) Escalamiento y fan out
7. 1 Escala horizontal
Mantenga la aplicación sin efectos para reiniciar. Estado de las conexiones: en la capa frontal; datos - del corredor.
Se necesita Sticky (hash por sesión/usuario) si hay colas locales.
Lo ideal es un frente sin estado: el nodo sólo multiplexa las suscripciones; los eventos provienen de pub/sub común.
7. 2 Pub/Sub y corredores
Para un amplio abanico de fans, utilice Kafka/NATS/Redis Streams.
La capa «Fanout Gateway» se suscribe a los topics y flecha a los clientes por WS/SSE.
Aplique la clave de enrutamiento (por ejemplo, 'userId', 'matchId') para equilibrar la carga entre nodos.
8) Confecciones de producción
8. 1 NGINX — WebSocket
nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; }
server {
listen 443 ssl http2;
server_name ws. example. com;
location /ws {
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1. 1;
proxy_read_timeout 75s; # increase for long sessions proxy_send_timeout 15s;
proxy_pass http://ws-backend;
}
}
8. 2 NGINX - SSE (es importante desactivar el búfer)
nginx location /events {
proxy_http_version 1. 1;
proxy_set_header Connection "";
proxy_buffering off; # is critical for proxy_cache off threads;
chunked_transfer_encoding on;
proxy_read_timeout 60m;
proxy_pass http://sse-backend;
}
8. 3 Kubernetes (Ingress Annotations, NGINX Ingress)
yaml metadata:
annotations:
nginx. ingress. kubernetes. io/proxy-read-timeout: "3600"
nginx. ingress. kubernetes. io/proxy-send-timeout: "3600"
nginx. ingress. kubernetes. io/enable-websocket: "true"
nginx. ingress. kubernetes. io/proxy-buffering: "off" # для SSE
9) Ejemplos de código
9. 1 Cliente SSE (navegador)
js const es = new EventSource("/events? channel=odds", { withCredentials: true });
es. addEventListener("message", (e) => {
const data = JSON. parse(e. data);
renderOdds(data);
});
es. addEventListener("error", () => {
//EventSource will reconnect itself; can be shown spinner
});
9. 2 Servidor SSE (Node. js/Express)
js app. get('/events', (req, res) => {
res. writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
res. write ('retry: 3000\n\n') ;//3s backoff to client
const sub = subscribe(req. query. channel, (event) => {
res. write(`id: ${event. id}\n`);
res. write(`event: message\n`);
res. write(`data: ${JSON. stringify(event. payload)}\n\n`);
});
req. on('close', () => sub. unsubscribe());
});
9. 3 Cliente de WebSocket (navegador)
js const ws = new WebSocket("wss://ws. example. com/ws");
ws. onopen = () => ws. send(JSON. stringify({ type: "join", room: "chat-1" }));
ws. onmessage = (m) => handle(JSON. parse(m. data));
ws. onclose = () => scheduleReconnect();
10) Seguridad y límite
Rate limiting per-connection y per-user: limite la frecuencia de los mensajes entrantes (WS) y la velocidad de flujo saliente (WS/SSE).
Quota según el tiempo de vida de la conexión y el tráfico total.
Message size limit и max messages/sec.
Filtros WAF/bot en la etapa de handshake; protección contra la conexión flooding (muchos conectores cortos).
Aislamiento por tenant/namespace: grupos de recursos individuales.
11) Observabilidad
Métricas:- `connections_active`, `connections_new_total`, `bytes_in/out`,
- `messages_in/out_total`, `dropped_messages_total`,
- `reconnects_total`, `latency_delivery_ms{p50,p95,p99}`.
- Registros: IP/UA, userId/tenantId, causa de cierre ('close _ code'), duración.
- Treking: asocie los eventos al comando original (ID de correlación); para WS, utilice los durmientes «virtuales» por batches.
12) Matices operativos
La terminación TLS está más cerca del cliente (CDN/edge).
Rotación proactiva de las conexiones (graceful) en los deployes: dar la bandera «codificación».
Charding por clave para distribuir de forma uniforme los canales «calientes».
Instantáneas de estado para suscriptores tardíos (snapshot + delta).
Caché de último valor (SSE especialmente): útil para clientes «fríos».
13) Anti-patrones
Stream de piezas binarias grandes a través de SSE o JSON por WS: utilice la descarga HTTP y el enlace en el mensaje.
La autorización es sólo en el momento de la conexión y no hay verificaciones de plumas en largas sesiones.
Un sticky global sin necesidad → desequilibrios y nodos «calientes».
Los timeouts/límites desactivados → los connects colgantes se comen la piscina.
El buffer proxy/CDN SSE → «tiempo real» se convierte en minutos de latencia.
Sin sequence/offset → después de reconnect el cliente pierde la integridad de los datos.
14) Lista de verificación de implementación
- Modelo seleccionado: WS (bidireccional) o SSE (unidireccional).
- Los temporizadores, keepalive y reconnect (backoff + jitter) están configurados.
- Diseñado por sequence/offset y (para SSE) 'id '/' Last-Event-ID'.
- Límites definidos: tamaño/velocidad/conexiones/cuotas, protección contra DoS.
- Configuración de proxy/ingress: upgrade, proxy_buffering off (SSE), read/nat timeouts.
- Escala: pub/sub-broker, fan-out gateway, sticky sólo cuando sea necesario.
- Autenticación: transmisión segura del token, rotación sin rotaciones.
- Observabilidad: métricas, registros, pistas; dashboards y alertas.
- Plan de lanzamiento: conexiones graceful-drain, señal al cliente sobre la pluma de conexión.
- Días de juego: acantilados de la red, caída de nodos, sobrecorriente, RTT largo.
15) FAQ
¿Es posible almacenar SSE en caché a través de CDN?
Normalmente no: es un flujo personalizado. Para los canales públicos - es posible con TTL corto y delivery-chunked, pero es fácil inventar «tiempo real».
¿WebSocket funciona en la parte superior de la HTTP/2/3?
El navegador WS comienza con HTTP/1. 1-upgrade; hay RFC 8441 para h2, el soporte en proxy/servidor se requiere por separado. Con h3 - en movimiento; para streaming en h3 hay WebTransporte, pero es una API diferente.
gRPC vs WS para el navegador?
El navegador limpio no dice gRPC; necesita gRPC-Web a través de Envoy. Para las IU interactivas, a menudo es más fácil WS + NAT.
16) Resultados
WebSocket - Cuando se necesita un diálogo en tiempo real y un canal bidireccional compacto.
SSE - Cuando se necesita un inserto simple y confiable de servidor a cliente, complejidad mínima y reconnect automático.
El éxito en la venta son los tiempos y límites correctos, la recuperación con offset, pub/sub-fan-out, la configuración correcta del proxy y la observabilidad clara.