4 điểm bởi GN⁺ 2024-03-19 | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong ứng dụng web thời gian thực, việc chọn giữa Long Polling, WebSockets, SSE, WebRTC, WebTransport để truyền sự kiện giữa máy chủ và máy khách sẽ tạo ra khác biệt lớn về độ trễ, tính hai chiều, độ khó triển khai và các ràng buộc vận hành
  • WebSockets cung cấp giao tiếp hai chiều qua một kết nối dài hạn duy nhất, nhưng trong vận hành thực tế thường phải dùng thêm thư viện như Socket.IO do cần xử lý phát hiện mất kết nối, tự kết nối lại và heartbeat ping-pong
  • Server-Sent Events là luồng một chiều máy chủ → máy khách dựa trên HTTP nên đơn giản hơn trong triển khai và xử lý tái kết nối, nhưng API EventSource mặc định bị hạn chế trong việc gửi body POST hoặc header tùy chỉnh
  • WebTransport hỗ trợ đa luồng cùng truyền tải tin cậy và không tin cậy trên nền HTTP/3 QUIC, nhưng tính đến tháng 3/2024 vẫn ở trạng thái Working Draft và chưa có hỗ trợ native trên Safari hay Node.js, nên vẫn khó xem là lựa chọn phổ thông
  • Việc ứng dụng di động bị hệ điều hành dừng ở nền, giới hạn số kết nối trên mỗi domain, proxy/tường lửa doanh nghiệp, và việc mất sự kiện khi đang tái kết nối khiến ứng dụng thực tế cần thêm logic khôi phục đồng bộ và kiểm thử hạ tầng

Diễn tiến của các công nghệ giao tiếp thời gian thực giữa máy chủ và máy khách

  • Trong ứng dụng web thời gian thực, khả năng để máy chủ gửi sự kiện tới máy khách đã trở thành một yêu cầu cốt lõi
  • Ban đầu, Long Polling chạy trên HTTP được dùng như cách nhắn tin máy chủ-khách khả thi trong trình duyệt
  • Sau đó, WebSockets xuất hiện như một cách chắc chắn hơn cho giao tiếp hai chiều
  • Server-Sent Events (SSE) cung cấp giao tiếp một chiều từ máy chủ đến máy khách theo cách đơn giản hơn
  • WebTransport có tiềm năng trở thành phương án hiệu quả hơn, linh hoạt hơn và mở rộng tốt hơn, nhưng hiện phạm vi hỗ trợ còn hạn chế
  • WebRTC có thể được cân nhắc cho một số trường hợp ngách về sự kiện máy chủ-khách, nhưng mục đích của nó khác nên không được xem là lựa chọn chính

Long Polling

  • Long Polling là cách mô phỏng giao tiếp push từ máy chủ bằng các request XHR thông thường
  • Khi máy khách mở một request tới máy chủ, máy chủ sẽ giữ phản hồi lại cho đến khi có dữ liệu mới
  • Sau khi gửi thông tin mới, kết nối sẽ đóng lại và máy khách lập tức bắt đầu request tiếp theo
  • So với polling định kỳ truyền thống, cách này cập nhật nhanh hơn và có thể giảm lưu lượng mạng không cần thiết cũng như tải máy chủ
  • Tuy vậy, nó kém hiệu quả hơn các công nghệ thời gian thực như WebSockets và có thể phát sinh độ trễ tùy thời điểm truyền dữ liệu
  • Phần triển khai phía máy khách khá đơn giản, nhưng ở backend rất khó bảo đảm máy khách đang tái kết nối không bị lỡ sự kiện

WebSockets

  • WebSockets tạo một kết nối dài hạn duy nhất giữa máy khách và máy chủ, đồng thời cung cấp giao tiếp song công hoàn toàn (full-duplex)
  • Sau khi thiết lập kết nối, hai phía có thể gửi dữ liệu độc lập mà không cần chịu overhead của chu kỳ request-response HTTP
  • Nó phù hợp với các ứng dụng cần độ trễ thấp và cập nhật thường xuyên như chat thời gian thực, game và nền tảng giao dịch tài chính
  • API WebSocket cơ bản khá dễ dùng, nhưng trong môi trường production việc xử lý mất kết nối và tạo lại kết nối trở nên phức tạp
  • Do khó phát hiện liệu kết nối còn dùng được hay không, người ta thường thêm heartbeat ping-and-pong
  • Vì sự phức tạp này, trong nhiều trường hợp người ta dùng thư viện như Socket.IO, và Socket.IO cũng có thể cung cấp fallback sang Long Polling nếu cần

Server-Sent Events

  • Server-Sent Events (SSE) là cách chuẩn để đẩy cập nhật từ máy chủ tới máy khách trên nền HTTP
  • Không giống WebSockets, nó chỉ được thiết kế cho giao tiếp một chiều máy chủ → máy khách
  • Nó phù hợp với các tình huống mà máy khách không cần gửi thông điệp ngược lại cho máy chủ, như news feed trực tiếp, tỷ số thể thao hoặc cập nhật thời gian thực
  • Có thể xem SSE như việc giữ một kết nối HTTP mở và để backend stream từng dòng phản hồi mỗi khi có sự kiện
  • Trên trình duyệt, máy khách khởi tạo một instance EventSource để nhận luồng sự kiện
  • EventSource tự động kết nối lại khi bị ngắt, khác với WebSockets
  • Máy chủ phải đặt header Content-Typetext/event-stream và định dạng các trường như loại sự kiện, payload dữ liệu, ID sự kiện và thời gian retry theo SSE specification

WebTransport

  • WebTransport là API cho giao tiếp hiệu quả, độ trễ thấp giữa máy khách web và máy chủ
  • Nó tận dụng HTTP/3 QUIC protocol để gửi dữ liệu trên nhiều stream
  • Nó hỗ trợ đồng thời truyền tải tin cậy, truyền tải không tin cậy và truyền dữ liệu không theo thứ tự
  • Nó có thể trở thành công cụ mạnh cho các ứng dụng cần mạng hiệu năng cao như game thời gian thực, live streaming và nền tảng cộng tác
  • Tính đến tháng 3/2024, WebTransport đang ở trạng thái Working Draft và chưa được hỗ trợ rộng rãi
  • Nó vẫn chưa dùng được trên Safari browser, và Node.js cũng chưa có hỗ trợ native
  • Kể cả khi hỗ trợ rộng hơn, API này vẫn rất phức tạp, nên có khả năng nó sẽ được dùng thông qua thư viện xây trên WebTransport thay vì được gọi trực tiếp trong mã ứng dụng

WebRTC

  • WebRTC là một dự án mã nguồn mở kiêm tiêu chuẩn API cung cấp khả năng giao tiếp thời gian thực trong trình duyệt và ứng dụng di động mà không cần plugin
  • Nó hỗ trợ kết nối peer-to-peer để trao đổi âm thanh, video và dữ liệu giữa các trình duyệt
  • Để đi qua NAT và tường lửa, nó sử dụng các giao thức như ICE, STUN và TURN
  • WebRTC được tạo ra cho tương tác client-client, nhưng cũng có thể được dùng cho giao tiếp máy chủ-khách nếu khiến máy chủ hoạt động như một client
  • Cách này chỉ phù hợp với các trường hợp ngách nên bị loại khỏi so sánh các lựa chọn chính
  • Để WebRTC hoạt động, vẫn cần một signaling server, và máy chủ này lại chạy trên một trong các công nghệ như WebSockets, SSE hoặc WebTransport
  • Vì vậy, WebRTC khó có thể được xem là phương án thay thế trực tiếp cho các công nghệ đó

Các ràng buộc chính theo từng công nghệ

  • Truyền dữ liệu hai chiều

    • Chỉ WebSocketsWebTransport hỗ trợ nhận dữ liệu từ máy chủ và gửi dữ liệu từ máy khách trên cùng một kết nối
    • Về lý thuyết Long Polling cũng làm được, nhưng để gửi dữ liệu mới trên một kết nối long-polling đang tồn tại thì phải thêm request HTTP khác nên không được khuyến nghị
    • Với Long Polling, tốt hơn là gửi dữ liệu máy khách → máy chủ bằng request HTTP riêng mà không làm ảnh hưởng kết nối hiện có
    • SSE không hỗ trợ gửi thêm dữ liệu về máy chủ
    • API EventSource API native mặc định cũng không cho gửi dữ liệu trong HTTP body như POST ngay cả ở request ban đầu
    • Dữ liệu phải được đưa vào URL parameter, nhưng credentials có thể bị lộ trong log máy chủ, proxy hoặc cache nên không tốt về mặt bảo mật
    • RxDB dùng eventsource polyfill thay cho EventSource API native để tránh vấn đề này, và thư viện đó bổ sung các tính năng như custom HTTP header
    • fetch-event-source của Microsoft cho phép gửi dữ liệu trong body và dùng request POST thay vì GET
  • Giới hạn số kết nối trên mỗi domain

    • Hầu hết trình duyệt hiện đại chỉ cho phép 6 kết nối trên mỗi domain, và giới hạn này làm giảm tính khả dụng của các cách nhắn tin ổn định từ máy chủ → máy khách nói chung
    • Giới hạn 6 kết nối còn được chia sẻ giữa các tab trình duyệt, nên nếu cùng một trang được mở ở nhiều tab thì các tab phải dùng chung một pool kết nối
    • RFC của HTTP/1.1 còn khuyến nghị con số thấp hơn, chỉ 2 kết nối trên mỗi máy chủ hoặc proxy
    • Chính sách này hợp lý để ngăn DDoS từ phía người truy cập, nhưng có thể gây vấn đề cho các giao tiếp máy chủ-khách hợp lệ cần nhiều kết nối
    • Để lách giới hạn, có thể dùng HTTP/2 hoặc HTTP/3 để trình duyệt chỉ mở một kết nối trên mỗi domain và xử lý dữ liệu bằng multiplexing
    • Ngay cả với HTTP/2 và HTTP/3, thiết lập SETTINGS_MAX_CONCURRENT_STREAMS vẫn giới hạn số stream đồng thời thực tế, và mặc định ở hầu hết cấu hình là 100 concurrent streams
    • Trình duyệt có thể tăng giới hạn kết nối cho một số API cụ thể như EventSource, nhưng các issue liên quan của ChromiumFirefox đều được đánh dấu là “won’t fix”
  • Giảm số lượng kết nối trong ứng dụng trình duyệt

    • Trong ứng dụng trình duyệt, cần giả định rằng người dùng có thể mở ứng dụng đồng thời ở nhiều tab
    • Mặc định, mỗi tab có thể mở một kết nối stream riêng tới máy chủ, nhưng trong đa số trường hợp điều đó là không cần thiết
    • Có thể chỉ mở một kết nối duy nhất dù có nhiều tab, rồi chia sẻ nó giữa các tab
    • RxDB dùng broadcast-channel npm package cùng LeaderElection để chỉ giữ một replication stream giữa máy chủ và máy khách
    • Gói này cũng có thể được dùng độc lập trong các ứng dụng khác mà không cần RxDB

Ràng buộc vận hành từ thiết bị di động, proxy và tường lửa

  • Trên các hệ điều hành di động như Android và iOS, rất khó duy trì các kết nối mở liên tục, bao gồm cả WebSockets
  • Sau một khoảng thời gian không hoạt động, hệ điều hành di động có thể đưa ứng dụng xuống nền và đóng các kết nối đang mở
  • Hành vi này là một phần của chiến lược quản lý tài nguyên nhằm tiết kiệm pin và tối ưu hiệu năng
  • Vì vậy, khi máy chủ cần gửi dữ liệu tới máy khách, các nhà phát triển thường dùng thông báo đẩy trên di động thay vì kết nối duy trì liên tục
  • Thông báo đẩy cho phép máy chủ báo dữ liệu mới cho ứng dụng và kích hoạt hành vi hay cập nhật của ứng dụng mà không cần giữ kết nối mở liên tục
  • Trong môi trường doanh nghiệp, proxy và tường lửa có thể chặn các kết nối không phải HTTP, khiến việc đưa máy chủ WebSocket vào hạ tầng trở nên khó khăn
  • Trong những môi trường như vậy, SSE dựa trên HTTP có thể là cách dễ tích hợp hơn với hệ thống doanh nghiệp
  • Long Polling cũng là một lựa chọn vì nó chỉ dùng request HTTP thông thường

So sánh hiệu năng

  • Khi so sánh WebSockets, SSE, Long Polling và WebTransport, cần xem đồng thời độ trễ, thông lượng, tải máy chủ và khả năng mở rộng
  • Bản demo kiểm tra thời gian thông điệp trong realtime-web repo với triển khai máy chủ Go cho thấy hiệu năng của WebSockets, WebRTC và WebTransport là tương đương
  • Vì WebTransport là công nghệ mới trên nền HTTP/3, có thể sau tháng 3/2024 sẽ còn xuất hiện thêm nhiều tối ưu hiệu năng
  • WebTransport được tối ưu để giảm tiêu thụ điện năng, nhưng chỉ số này chưa được kiểm thử
  • Độ trễ

    • WebSockets cho độ trễ thấp nhất nhờ giao tiếp full-duplex trên một kết nối duy trì duy nhất
    • SSE cũng cho độ trễ thấp trong giao tiếp máy chủ → máy khách, nhưng nếu máy khách cần gửi thông điệp lên máy chủ thì phải dùng thêm request HTTP
    • Long Polling có độ trễ cao hơn vì phải tạo kết nối HTTP mới cho mỗi lần truyền dữ liệu
    • Trong Long Polling, nếu đúng lúc máy chủ muốn gửi sự kiện mà máy khách đang mở kết nối mới thì độ trễ có thể tăng mạnh
    • WebTransport được kỳ vọng có độ trễ thấp tương đương WebSockets, đồng thời tận dụng multiplexing và congestion control hiệu quả hơn của HTTP/3
  • Thông lượng

    • WebSockets có thể đạt thông lượng cao nhờ kết nối duy trì, nhưng vấn đề backpressure khi máy khách không xử lý kịp tốc độ gửi từ máy chủ có thể ảnh hưởng đến thông lượng
    • SSE có overhead thấp hơn WebSockets nên trong các tình huống broadcast một chiều từ máy chủ → máy khách, nó có thể đạt thông lượng cao hơn
    • Long Polling thường có thông lượng thấp và tiêu tốn nhiều tài nguyên máy chủ hơn do overhead từ việc liên tục mở rồi đóng kết nối
    • WebTransport được kỳ vọng hỗ trợ thông lượng cao cho cả stream một chiều và hai chiều trong một kết nối duy nhất, và có thể vượt WebSockets ở những kịch bản cần nhiều stream
  • Khả năng mở rộng và tải máy chủ

    • WebSockets có thể làm tăng đáng kể tải máy chủ khi phải duy trì nhiều kết nối, từ đó ảnh hưởng đến khả năng mở rộng của ứng dụng có lượng người dùng lớn
    • SSE mở rộng tốt hơn trong các kịch bản chủ yếu cần cập nhật máy chủ → máy khách
    • SSE dùng request HTTP thông thường mà không cần quy trình WebSocket như protocol upgrade, nên overhead kết nối thấp hơn
    • Long Polling có tải máy chủ cao nhất do phải thiết lập kết nối thường xuyên, nên khả năng mở rộng thấp nhất và chỉ phù hợp làm cơ chế fallback
    • WebTransport được thiết kế hướng tới khả năng mở rộng cao dựa trên hiệu quả xử lý kết nối và stream của HTTP/3, và có thể giảm tải máy chủ tốt hơn WebSockets lẫn SSE

Khuyến nghị theo trường hợp sử dụng

  • SSE là lựa chọn trực quan nhất trong triển khai, dùng giao thức HTTP/S hiện có nên dễ tránh các giới hạn tường lửa doanh nghiệp và các vấn đề kỹ thuật có thể phát sinh với giao thức khác
  • Nó dễ tích hợp với Node.js và các framework máy chủ khác
  • Nó phù hợp với các ứng dụng cần cập nhật thường xuyên theo hướng máy chủ → máy khách như news feed, giá cổ phiếu và live event streaming
  • WebSockets mạnh ở các kịch bản cần giao tiếp hai chiều liên tục
  • Với các trường hợp cần tương tác liên tục như game trên trình duyệt, ứng dụng chat hoặc cập nhật thể thao trực tiếp, đây là lựa chọn chính
  • WebTransport có nhiều tiềm năng nhưng hỗ trợ trong framework máy chủ chưa rộng, đồng thời thiếu tương thích với Node.jsSafari
  • WebTransport phụ thuộc vào HTTP/3, trong khi hỗ trợ HTTP/3 của nhiều web server như nginx vẫn đang ở trạng thái experimental
  • Đây là công nghệ hướng tương lai vì hỗ trợ cả truyền dữ liệu tin cậy và không tin cậy, nhưng với đa số trường hợp sử dụng hiện nay thì vẫn chưa phải lựa chọn khả thi
  • Long Polling nhìn chung là cách làm cũ do kém hiệu quả và có overhead cao vì phải lặp lại việc thiết lập kết nối HTTP mới
  • Nó có thể được dùng làm fallback ở những môi trường không hỗ trợ WebSockets hoặc SSE, nhưng nhìn chung không được khuyến nghị vì hạn chế hiệu năng

Vấn đề mất sự kiện khi đang tái kết nối

  • Khi xây dựng tính năng trên bất kỳ công nghệ streaming thời gian thực nào, cần tính đến việc ngắt kết nối và tái kết nối
  • Nếu máy khách đang kết nối lại hoặc ở trạng thái offline, nó có thể không nhận được các sự kiện phát sinh từ máy chủ qua stream
  • Nếu máy chủ stream toàn bộ nội dung mỗi lần như giá cổ phiếu, việc lỡ sự kiện có thể không quan trọng
  • Nhưng nếu backend chỉ stream các kết quả từng phần, thì bắt buộc phải xử lý sự kiện bị mất
  • Việc backend phải ghi nhớ sự kiện nào đã được gửi thành công cho từng máy khách là cách làm kém khả năng mở rộng
  • Tốt hơn là xử lý vấn đề này bằng logic phía máy khách
  • RxDB Sync Engine dùng hai chế độ hoạt động
    • checkpoint iteration mode: lặp lại việc truy vấn dữ liệu backend bằng request HTTP thông thường để máy khách bắt kịp cho tới khi đồng bộ lại
    • event observation mode: dùng các cập nhật từ stream thời gian thực để giữ máy khách ở trạng thái đồng bộ
  • Khi kết nối của máy khách bị ngắt hoặc phát sinh lỗi, replication sẽ tạm chuyển về checkpoint iteration mode để đồng bộ lại cho đến khi trở về cùng trạng thái với máy chủ
  • Cách làm này bù lại các sự kiện bị mất và giúp máy khách luôn đồng bộ chính xác với máy chủ

Những điều cần xác minh trong hạ tầng doanh nghiệp

  • Trong hạ tầng doanh nghiệp, các công nghệ streaming nói chung đều có thể gặp vấn đề
  • Proxy và tường lửa có thể chặn lưu lượng hoặc vô tình làm hỏng request và response
  • Vì vậy, khi triển khai ứng dụng thời gian thực trong những môi trường này, trước hết cần kiểm thử xem công nghệ đã chọn có thực sự hoạt động trong hạ tầng đó hay không

1 bình luận

 
GN⁺ 2024-03-19
Các ý kiến trên Hacker News
  • Tôi luôn có thiện cảm với Server-Sent Events. Nó đơn giản, dễ dùng/dễ triển khai

    • Nếu có IPv6 thì giờ có thể mở rộng hoàn toàn rất dễ; nếu thiết kế đúng, chỉ cần đưa cho client danh sách dịch vụ SSE là nó hoạt động gần như phi trạng thái, nên dễ mở rộng hơn nhiều
      WebSocket trở nên khá phức tạp để mở rộng khi mức sử dụng vượt qua một ngưỡng nào đó
    • Nhờ sự đơn giản, có thể mở rộng bằng CDN dễ hơn nhiều so với WebSocket: https://www.fastly.com/blog/server-sent-events-fastly
    • Đồng ý. Tuy nhiên, mỗi instance trình duyệt bị giới hạn 6 luồng SSE cho mỗi origin, nên nếu không thêm độ phức tạp phía client thì 6 tab có thể là giới hạn
      https://crbug.com/275955
    • Nhược điểm là phải mã hóa payload bằng base64 hoặc loại bỏ ký tự xuống dòng
      Tôi tự hỏi vì sao họ không làm nó thành response streaming dạng multipart. Dạng này cũng hỗ trợ metadata và là một định dạng được triển khai rất phổ biến
    • Nó chạy được cả trên Apache prefork và PHP thông thường
  • Có thêm vài nhược điểm cần biết
    WebSocket không có kiểm soát luồng (backpressure) và multiplexing, nên nếu cần thì bạn phải tự làm hoặc dùng thứ như RSocket. SSE cũng không thể gửi trực tiếp dữ liệu nhị phân, nên cần mã hóa kiểu base64
    WebTransport xử lý các vấn đề này và cũng giải quyết HOL blocking, nhưng tôi lo nó sẽ gặp vấn đề giống chuyển đổi Python 2→3 hay IPv6: mọi người dễ tiếp tục dùng phiên bản cũ và cảm thấy lợi ích nâng cấp không đáng kể
    Chừng nào trình duyệt còn tiếp tục hoạt động bằng TCP, một số mạng có thể chặn hẳn UDP, và do đó chặn HTTP/3/WebTransport

    • HOL blocking là vấn đề, nhưng TCP cung cấp kiểm soát luồng. Nếu không dùng nó thì tức là chuyển lên trên HTTP/3
      Nỗi lo rằng việc chuyển sang WebTransport có thể chậm cũng là điều từng có thể nói y như vậy về vận chuyển TLS, HTTP/3 và XHR trước đây. Vì cấu trúc thị trường bị chi phối bởi vài engine trình duyệt lớn, việc triển khai tính năng trình duyệt và giao thức mới tương đối dễ
      Lập luận rằng vì TCP dùng được nên một số mạng sẽ chặn UDP cũng giống như nói rằng vì HTTP 1.1 không TLS dùng được nên HTTP/2 và TLS cũng sẽ tiếp tục bị chặn. Không hoàn toàn sai, nhưng nhìn vào mức độ chấp nhận rộng rãi của HTTP/2 và đặc biệt là TLS thì có vẻ đây không phải vấn đề lớn như tưởng tượng
    • Tôi nghe mãi chuyện các mạng chặn UDP nhưng chưa từng thực sự thấy. Có rất nhiều thứ chạy trên UDP
      Một văn phòng nhỏ hoặc môi trường doanh nghiệp phản địa đàng như trong phim có thể đóng nó lại, nhưng tôi không hiểu việc một số mạng có thể cấm UDP thì liên quan lớn đến vậy ở điểm nào. Một số mạng còn chặn cả google.com hay wikipedia.com, nhưng điều đó không khiến các dịch vụ ấy thất bại
    • WebSocketStream sắp vào Chrome và bổ sung backpressure: https://chromestatus.com/feature/5189728691290112
    • Có một cách tiếp cận tốt hơn base64 là binary-sse: https://github.com/luciopaiva/binary-sse
    • Trong tình huống như vậy, HTTP/2 vẫn sẽ hoạt động, nên nỗi lo về multiplexing giảm đi
  • Phần giải thích về WebRTC trong bài không chính xác. WebRTC client/server vẫn có thể làm mà không cần “máy chủ signaling” riêng; server chỉ cần đảm nhiệm signaling là được
    Chỉ cần thêm vài lượt round-trip, chứ không cần máy chủ riêng. Data channel của WebRTC hoạt động khá tốt như một giải pháp thay thế WebSocket hoặc SSE, đặc biệt khi muốn tránh HOL blocking. Cũng có nhiều thư viện như Pion hay str0m gần như làm hộ mọi việc
    Nói API WebTransport phức tạp cũng có vẻ phóng đại. Nếu không cần tính năng nâng cao thì cứ bỏ qua; nếu muốn dùng giống WebSocket thì chỉ cần mở một stream hai chiều là gần như xong. Muốn tránh HOL blocking thì mở một stream cho mỗi message. Phức tạp hơn một chút, nhưng chưa đến mức nhất thiết phải có thư viện, và GitHub Copilot rất có thể cũng viết code giúp được. Tuy nhiên WebTransport vẫn đang trong quá trình trưởng thành nên chưa có nhiều thư viện server, và vẫn đang chờ Safari hỗ trợ

    • Tôi thấy khó hiểu khi nói WebRTC client/server có thể làm mà không cần “máy chủ signaling”
      Thông thường máy chủ signaling được triển khai bằng WebSocket. Nếu không phải bạn đang đề xuất bootstrap phi tập trung cho các client hiện có, thì không thể tự triển khai bằng chính WebRTC
  • Nếu đang xây cho khách hàng có hạ tầng IT “doanh nghiệp” và “bảo mật” truyền thống, tốt hơn là thêm nút refresh rồi kết thúc ở đó
    Theo kinh nghiệm của tôi trong các môi trường như vậy, thứ liên tục thất bại và cũng không thể sửa vì các quy trình vô tận chính là nỗ lực xây tính năng thời gian thực cho những khách hàng kiểu này

    • Jetty/CometD sẽ fallback sang long polling nếu các phương thức truyền tải khác không dùng được
    • Thành thật mà nói, toàn bộ nhóm công nghệ này cái nào cũng có vấn đề riêng, và nút refresh cũng không ngoại lệ
    • Trình duyệt có nút refresh, nhưng có lẽ nếu bấm vào đó thì ứng dụng sẽ bị hỏng
  • WebSocket và SSE trở thành vấn đề đau đầu lớn về quản lý khi mở rộng quy mô. Đặc biệt ở backend cần khả năng quan sát riêng, và nếu không triển khai thật cẩn thận trên thiết bị di động thì việc debug frontend sẽ thành ác mộng
    Thiết bị thường tắt hoặc làm chậm mạng để tiết kiệm pin, nhất là khi không thực hiện I/O một cách tường minh qua API chuyên dụng
    Tạo kết nối mới là thao tác tốn kém, và server phải lưu trạng thái ở đâu đó. Nếu tầng lưu trạng thái này gặp vấn đề, client sẽ liên tục thử lại, bị timeout và mắc kẹt mãi trong các thao tác tốn kém. Đây cũng không phải cách dễ kiểm soát thông lượng trong khi tạo tải lên cơ sở dữ liệu một cách từ từ
    Về độ tin cậy, theo kinh nghiệm thì long polling là tốt nhất. Ngay cả khi luồng dựa trên sự kiện thực sự quan trọng, frontend nên long poll tới backend tầng 1, rồi tầng 1 đó subscribe tới backend tầng 2 bằng WebSocket; cấu trúc 2 tầng như vậy tốt hơn và kiểm soát độ tin cậy tốt hơn nhiều

    • Hoàn toàn đồng ý. Tôi đã thấy nhiều trường hợp tự bắn vào chân với WebSocket và SSE. Long polling có chi phí, nhưng tôi xem đây là cách tiếp cận dễ giải thích và dễ mở rộng nhất
    • SSE hỗ trợ long polling. Có thể để server đóng kết nối khi muốn
      SSE hỗ trợ tự động kết nối lại và còn kèm ID đã thấy gần nhất để server có thể tiếp tục liền mạch
    • Bài viết được liên kết đã đề cập khá nhiều phần như vậy, và rxdb có các cơ chế giúp giảm nhẹ nhiều mối lo ngại
  • Bài viết không nhắc tới, nhưng short polling cũng liên quan. Dù không phải cách gửi thông điệp từ server tới client, nó vẫn hữu ích khi không có lựa chọn nào khác, chẳng hạn trên shared hosting
    Theo kinh nghiệm của tôi, ngay cả khi khoảng thời gian polling dài, ví dụ 20 giây, nếu mỗi phản hồi kèm theo danh sách thông điệp thì vẫn hoạt động khá tốt. Khi người dùng bấm nút, client gửi yêu cầu tới server, server phản hồi kèm dữ liệu và danh sách thông điệp mới nhất để client được cập nhật

    • Cách này cũng áp dụng được cho dữ liệu thay đổi nhanh. Đặc biệt nếu tỷ lệ các lần polling có chứa cập nhật là cao
  • Đến giờ tôi vẫn không hiểu vì sao WebSocket và SSE không hỗ trợ gửi các header như Authorization trong yêu cầu ban đầu. Chúng đẩy toàn bộ phần xác thực dịch vụ thời gian thực cho người triển khai
    Có thể trong spec có cách hay, nhưng tôi đã thấy quá nhiều cách tiếp cận khác nhau đến mức giờ gần như có thể nói là không có

    • EventSource API có nhiều điểm đáng tiếc. Tôi là maintainer của polyfill EventSource được dùng nhiều nhất, nhưng gần đây đã bắt đầu một dự án viết lại client EventSource theo hướng hiện đại: https://github.com/rexxars/eventsource-client
      Không chỉ xử lý header tùy chỉnh, nó còn hỗ trợ mọi method yêu cầu (POST, PATCH, v.v.), kèm request body, subscribe các event có tên, và đặt last event ID ban đầu. Cũng có thể dùng như async iterator
      Tôi thích sự đơn giản của Server-Sent Events, nhưng API EventSource trông như được triển khai vội rồi cứ thế để lại
      [1]: https://github.com/eventsource/eventsource
    • Chẳng phải yêu cầu ban đầu có thể gửi toàn bộ HTTP header tiêu chuẩn và cả cookie sao?
    • Cookie thì có gửi
    • Cũng còn có chứng chỉ TLS nữa
    • Tôi đã dùng nó vài năm rồi, có phải tôi đang bỏ sót điều gì không?
  • Có thể là suy nghĩ ngây thơ, nhưng nếu giả định dùng HTTP/2 trở lên, thì kết hợp EventSource với fetch() để gửi thông điệp có vẻ cũng ổn không kém các giao thức khác dùng một kết nối TCP duy nhất. HTTP/3 dùng UDP nên còn tốt hơn
    Tiền đề là chỉ cần duy trì kết nối khi tab ở foreground. Tôi tò mò khi thực sự thử cách này thì đã gặp vấn đề gì

    • Một hạn chế là SSE chỉ dành cho văn bản, nên không thể gửi dữ liệu nhị phân một cách hiệu quả. Cần mã hóa kiểu base64
    • Có thư viện làm đúng thứ bạn muốn
      https://www.npmjs.com/package/@microsoft/fetch-event-source
    • Tôi cũng đã nghĩ y hệt như vậy. Tôi tự hỏi liệu HTTP/2 và SSE có giải quyết được 99% vấn đề không
      Thay vì làm một thứ hoàn toàn khác, tôi đã tò mò liệu có thể đẩy SSE đi xa hơn với độ trễ, mức dùng bộ nhớ và tài nguyên CPU thấp hơn không
    • Nếu tiền đề là trường hợp sử dụng chính là server→client thì đúng
  • Đọc những bài như thế này khá thú vị. Cuối thập niên 90, tôi thiết kế một hệ thống đấu giá trực tuyến, và khi đó hoàn toàn không có yêu cầu XHR
    Cập nhật thời gian thực đều được xử lý bằng server-push/HTTP streaming. Khi ấy không dễ xử lý tất cả các kết nối mở, nhưng với kiến trúc phù hợp thì vẫn có thể đạt quy mô chấp nhận được

    • Tôi đã mất rất nhiều thời gian để giải thích cho mọi người tầm quan trọng của HTTP streaming, và chắc chắn đó không phải cuộc chiến dễ dàng
      Lợi ích của HTTP/2 hay HTTP/3 rất tuyệt, nhưng cũng cần biết rằng có những thứ có thể tận dụng ngay trên HTTP 1.1 vốn được hỗ trợ gần như ở khắp nơi
  • Có hơi nhớ long polling. So với các công nghệ mới thì nó thật sự đơn giản. Ngay cả khi cho rằng WebRTC là tốt nhất, tôi vẫn cảm thấy vậy

    • SSE cũng không phức tạp hơn long polling là mấy. Khác biệt chỉ là server không đóng kết nối ngay sau khi gửi phản hồi
      Thay vào đó, nó lại chờ dữ liệu rồi gửi thêm phản hồi trên cùng một stream
    • Ước gì nó đơn giản như vậy, nhưng không phải
      Phần networking của Second Life dùng HTTPS long polling cho “kênh sự kiện”, và server gửi các thông điệp sự kiện tới client qua kênh đó. Phần lớn thông điệp đi qua UDP, nhưng các thông điệp cần mã hóa hoặc có kích thước lớn thì đi qua kênh sự kiện HTTPS/TCP
      Ở phía client, client C++ dùng libcurl, nhưng thiết lập timeout mặc định không phù hợp với long polling. libcurl ngắt kết nối và tạo request mới, kết quả là có thể mất hoặc trùng lặp thông điệp
      Ở phía server, Apache đứng trước server mô phỏng thực tế để lọc các nỗ lực kết nối không liên quan, nhưng Apache cũng có timeout riêng nên nó ngắt kết nối và khiến client thử lại
      Họ cố ngăn mất mát bằng số thứ tự thông điệp, nhưng server Second Life bỏ qua số thứ tự mà client gửi lại để xác nhận. Một số server tương thích của Open Simulator thậm chí còn bỏ qua số thứ tự tuần tự
      Kết quả là một hệ thống dựa trên HTTPS có thể làm mất hoặc nhân đôi các thông điệp vốn cần phải đáng tin cậy. Nếu mất một số thông điệp, hoạt động của người dùng trong game sẽ bị khựng lại
      Những người thiết kế thứ này đã rời đi từ lâu, và các nhân viên hiện tại không biết mớ hỗn độn này nghiêm trọng đến mức nào. Người dùng bên ngoài đã phải tìm ra vấn đề và ghi lại tài liệu, còn nhân viên công ty đã cố sửa trong nhiều tháng. Có vẻ việc sửa đủ khó đến mức hiện tại họ đang có xu hướng trì hoãn
      Vì vậy long polling không “đơn giản đến mức ngốc nghếch”. Cách đúng có lẽ là gửi thông điệp keep-alive đủ thường xuyên để các lớp TCP và HTTPS không bị timeout. Như vậy Apache và libcurl sẽ ở trong đường chạy mà chúng hoạt động tốt
    • Vẫn còn được dùng thường xuyên. Có nhiều ứng dụng mà việc chấp nhận overhead của request để giữ mọi thứ trong ngữ cảnh HTTP API hiện có là hợp lý
    • Ngày nay vẫn có thể dùng long polling với HTTP/2, và nó sẽ không biến mất