- WebSocket hữu ích cho giao tiếp thời gian thực, nhưng không phải lúc nào cũng cần; các lựa chọn thay thế dựa trên HTTP có thể đơn giản và ổn định hơn
- Trong xử lý giao dịch, quản lý kết nối và độ phức tạp phía máy chủ, WebSocket có thể gây ra overhead quá mức
- Tận dụng HTTP Streaming và thư viện
eventkit có thể xử lý đồng bộ hóa thời gian thực và sự kiện mà không cần WebSocket
WebSocket là gì
- WebSocket là công nghệ mở một kênh giao tiếp hai chiều liên tục giữa client và server
- Kết nối được khởi tạo qua HTTP, nhưng sau đó việc giao tiếp diễn ra bằng một giao thức riêng
- Thường được dùng để triển khai ứng dụng thời gian thực và hữu ích nhờ khả năng giao tiếp hai chiều
Thông điệp WebSocket không theo mô hình giao dịch
- WebSocket không đảm bảo mối liên hệ trực tiếp giữa yêu cầu và phản hồi
- Lệnh thay đổi trạng thái và thông điệp kết quả của chúng có thể đến xen lẫn trong cùng một luồng
- Ví dụ, nếu một client thay đổi trạng thái và phát sinh lỗi, sẽ khó biết lỗi đó thuộc về lệnh nào
- Cách giải quyết là đưa kèm
requestId để liên kết lệnh và phản hồi, nhưng điều này làm tăng độ phức tạp và chi phí quản lý
- Cách đơn giản hơn là dùng mô hình giao dịch qua HTTP để gửi lệnh, còn WebSocket chỉ dùng để phát thông báo thay đổi trạng thái
- Có thể tách riêng phía gửi dùng HTTP request và phía nhận dùng WebSocket hoặc một phương thức streaming khác
Khó khăn trong quản lý vòng đời kết nối WebSocket
- Khi dùng WebSocket, bạn phải tự xử lý việc bắt đầu, kết thúc, lỗi và kết nối lại
- Ví dụ xử lý cơ bản trong trình duyệt bao gồm mở kết nối, nhận thông điệp, phát sinh lỗi và xử lý sự kiện đóng kết nối
- Cần thêm logic như tự động kết nối lại, buffering thông điệp, exponential backoff, v.v.
- Trong khi đó, HTTP có điểm bắt đầu và kết thúc rõ ràng theo từng request nên triển khai đơn giản hơn
- Việc quản lý vòng đời phức tạp như vậy chỉ hợp lý khi bạn thực sự có lý do rõ ràng để dùng WebSocket
Độ phức tạp của mã phía máy chủ tăng lên
- WebSocket cần xử lý yêu cầu nâng cấp HTTP, đòi hỏi thêm logic handshake
- Cần xác thực các header đặc biệt như
Sec-WebSocket-Key và trả về response header phù hợp
- Sau khi thiết lập kết nối WebSocket, phải duy trì trạng thái nhận/gửi thông điệp liên tục, và cũng có thể phát sinh các vấn đề như xử lý partial frame
- So với chỉ dùng HTTP, việc debug và xử lý lỗi trở nên khó hơn
- Dù framework có trừu tượng hóa một phần quy trình, độ phức tạp nền tảng vẫn còn đó
Giải pháp thay thế: HTTP Streaming
- HTTP vốn là giao thức hỗ trợ streaming, cho phép truyền luồng dữ liệu theo thời gian thực thay vì phải đợi toàn bộ tệp hoàn tất
- Có thể thay thế phần chức năng phía nhận của WebSocket hiện có bằng HTTP streaming
- Có thể dùng async generator để xử lý cập nhật trạng thái dưới dạng luồng
- Luồng phía server
- Cập nhật trạng thái được thực hiện trong hàm xử lý lệnh
- Các client đã kết nối sẽ nhận được giá trị mới mỗi khi nó xuất hiện thông qua generator
- Lệnh thay đổi trạng thái được gửi qua HTTP POST, còn luồng thời gian thực được đăng ký bằng request GET
- Luồng phía client
- Nhận dữ liệu thời gian thực qua Fetch API và Stream Reader
- Giải mã văn bản rồi cập nhật UI
- Với cấu trúc này, có thể triển khai đồng bộ hóa trạng thái thời gian thực mà không cần WebSocket
Bonus: Giới thiệu thư viện eventkit
eventkit là thư viện giúp dễ dàng xây dựng và quan sát các async stream
- Tương tự RxJS, nhưng được cải thiện về quản lý side effect và được thiết kế dựa trên generator
- Khi đẩy cập nhật trạng thái vào stream, client có thể nhận chúng theo thời gian thực
- Có thể triển khai đơn giản ở cả server và client thông qua
Stream và AsyncObservable
- Cách dùng eventkit phía server
- Đẩy thay đổi trạng thái vào Stream, client sẽ đăng ký stream tương ứng
- Cách dùng eventkit phía client
- Nhận dữ liệu stream, giải mã rồi cập nhật UI
- Cũng có sẵn kho GitHub chính thức và hướng dẫn về HTTP Streaming
GitHub: https://github.com/hntrl/eventkit
3 bình luận
Ý kiến trên Hacker News
Tôi không nghĩ HTTP streaming được thiết kế với kiểu mẫu này trong đầu. HTTP streaming dùng để chia dữ liệu lớn thành từng phần. Nếu dùng streaming như một cơ chế pub/sub thì có thể sẽ hối hận. Các bộ trung gian HTTP không lường trước kiểu lưu lượng này (NGINX, CloudFlare, v.v.). Mỗi khi kết nối Wi‑Fi bị ngắt, có vẻ như fetch API sẽ phát sinh lỗi do yêu cầu thất bại
Gửi RequestID lên server để có được chu kỳ request/response không phải là điều kỳ quặc hay quá mức. Với các ứng dụng nghiêm túc, luôn đáng để có API kiểu
send(message).then(res => ...)headers['authorization']trong yêu cầu WebSocket, lại phải truy cập đối tượngconnectionParamsvốn giả làm header của requestVideo streaming là việc client yêu cầu các chunk theo range, chứ không phải một kết nối HTTP đơn lẻ
Nên dùng SSE thay vì EventKit
Tôi định dùng form submit HTTP truyền thống trong POC. Không cần gì khác cả
Vấn đề của HTTP2 là server push được thêm chồng lên giao thức sẵn có. HTTP là giao thức truyền tài nguyên, nên thêm overhead không cần thiết. Mục tiêu chính của HTTP2 là để server đẩy trước file/tài nguyên tới client nhằm giảm độ trễ vòng lặp
WebSockets gửi theo datagram (packet), không phải theo stream. API WebSockets của các thư viện JavaScript không thể xử lý backpressure và cũng không thể xử lý mọi lỗi. Cần cẩn thận nếu muốn dùng như một TCP stream
Tôi đã hối hận sau khi triển khai WebSockets lên production. Có những vấn đề như NGINX đóng kết nối sau 4/8 giờ, trình duyệt không tự kết nối lại sau khi máy ngủ, v.v. Nếu có thể thì nên tránh WebSockets và các kết nối dài hạn
Có một cách nhìn quá lý tưởng về WebSockets. Mọi người có xu hướng dùng WebSockets cho các ca sử dụng streaming/thời gian thực. WebSockets làm mất đi sự đơn giản và các lợi ích của bộ công cụ HTTP. Giải pháp cho việc cập nhật streaming từ server là h2/h3 và SSE. Nếu có thể gom lô ở mức tối đa 0.5 req/s cho mỗi client thì không cần WebSockets
Những ai quan tâm đến HTTP streaming nên xem Braid-HTTP. Nó mở rộng HTTP một cách thanh lịch cho event streaming để cung cấp một giao thức đồng bộ trạng thái mạnh mẽ