Kết nối: client mở WebSocket (kênh song công, giữ mở) tới server. Khi có sự kiện, server push xuống thay vì client poll liên tục.
Auth: WebSocket không gửi header tùy ý như HTTP thường → xác thực bằng token trong query/subprotocol lúc handshake, hoặc dựa vào cookie phiên; validate rồi mới accept. Gắn connection với user_id.
Vấn đề khi scale ngang: mỗi user cắm vào một instance cụ thể. Nếu instance A cần đẩy tin cho user đang cắm ở instance B thì A không có socket đó.
- Giải pháp: Redis Pub/Sub (hoặc message broker) làm backplane. Instance nào có sự kiện thì publish lên một channel; mọi instance subscribe và instance nào đang giữ socket của user đó sẽ forward xuống client.
- Cần sticky session/routing hoặc lưu bảng user → instance để biết định tuyến.
// any instance with an event publishes to Redis
redis.publish("notify", JSON.stringify({ userId, payload }))
// every instance subscribes and forwards to the sockets it holds
sub.subscribe("notify")
sub.on("message", (_ch, raw) => {
const { userId, payload } = JSON.parse(raw)
socketsByUser.get(userId)?.forEach((ws) => ws.send(JSON.stringify(payload)))
})Fallback: trình duyệt cũ/proxy chặn WS → hạ xuống SSE (server→client một chiều, đủ cho notification) hoặc long-polling. Thêm heartbeat/ping để phát hiện kết nối chết và reconnect có backoff.
Lỗi hay gặp: quên auth lúc handshake; giả định một instance; không dọn socket chết gây rò rỉ.