ADR-0001. WebSocket mở rộng theo chiều ngang qua một bus Redis pub/sub duy nhất
| Trường | Giá trị |
|---|---|
| Status | Accepted |
| Date | 2026-03-28 |
| Deciders | Phat Nguyen |
| Supersedes | - |
Bối cảnh
- Cập nhật thời gian thực (order, payment) phải đến được client POS/Admin bất kể instance Signal nào đang giữ socket.
- Kết nối WebSocket gắn chặt với một process; trạng thái client trong bộ nhớ không thể chia sẻ giữa các replica.
- Các service khác (Sale, Payment) phải đẩy được dữ liệu tới client mà không tự dựng WebSocket server riêng.
- Chúng ta muốn một điểm biên thời gian thực duy nhất, không phải một WebSocket server nhúng trong mọi service.
Quyết định
Chúng ta sẽ chạy Signal là WebSocket server duy nhất và định tuyến mọi luồng phân phối xuyên instance / xuyên service qua một bus Redis pub/sub duy nhất.
- Signal chứa
WebSocketServerHelper(subscribe + publish); producer chỉ giữ mộtWebSocketEmitternhẹ (chỉ publish). sendToClientgửi cục bộ nếu client nằm trên instance này, nếu không sẽ publish lại lên Redis cho instance đang sở hữu client đó.broadcast/sendToRoomluôn publish lên Redis để mỗi instance phân phối tới các client khớp ở phía cục bộ của nó.- Redis chạy ở chế độ
singlehoặccluster(APP_ENV_WEBSOCKET_REDIS_*); mọi bên tham gia phải dùng chung một instance.
Hệ quả
| Ưu | Nhược |
|---|---|
| Replica không trạng thái - mở rộng theo chiều ngang sau Traefik | Redis trở thành phụ thuộc cứng / điểm lỗi đơn cho fan-out |
| Producer giữ gọn nhẹ (chỉ emitter, không có WS server) | Phân phối xuyên instance chỉ ở mức cố gắng tối đa (bỏ qua nếu không instance nào subscribe) |
| Một điểm biên thời gian thực duy nhất để bảo mật và vận hành | Mọi publisher phải cấu hình trỏ về cùng một Redis y hệt |
Phương án đã cân nhắc
| Phương án | Ưu | Nhược | Vì sao loại |
|---|---|---|---|
| WS server nhúng trong mỗi service | Không cần bus dùng chung | N server cần bảo mật; client phải quản lý N socket | Vận hành nặng nề, lặp lại auth/mã hoá |
| Phiên gắn chặt (sticky), không bus | Đơn giản | Phá vỡ broadcast + phân phối xuyên instance | Không đáp ứng yêu cầu "đến được mọi client" |
| Dùng Kafka làm bus phân phối | Đã có sẵn | Độ trễ cao hơn, không thiết kế cho fan-out pub/sub tức thời | Redis pub/sub phù hợp phân phối trực tiếp hơn |
Tham chiếu
signal/src/components/websocket.component.ts(NxWebSocketComponent,bindEmitter)signal/src/services/signal-event.service.ts(định tuyến cục bộ/từ xa củasendToClient)- Operations - fan-out đa instance