Ngừng Polling Liên Tục: Chuyển Sang Server-Sent Events

Ngừng Polling Liên Tục: Chuyển Sang Server-Sent Events

Ngừng lãng phí tài nguyên server với HTTP polling. Hướng dẫn dùng Server-Sent Events (SSE) cập nhật dữ liệu real-time chuẩn xác và nhẹ nhàng.

Khi xây dựng các ứng dụng cần cập nhật thông tin theo thời gian thực—chẳng hạn như bảng tin thông báo, theo dõi tiến độ đơn hàng, dashboard giám sát hệ thống hay thanh trạng thái công việc nền—nhiều lập trình viên thường chọn giải pháp HTTP polling theo thói quen. Việc dựng một hàm hẹn giờ gửi request định kỳ tới API server có vẻ rất nhanh chóng và dễ làm ở giai đoạn thử nghiệm ban đầu.

Tuy nhiên, khi lượng người dùng tăng trưởng, polling ngây thơ sẽ nhanh chóng lộ diện là "kẻ sát nhân thầm lặng" tiêu tốn tài nguyên hệ thống, làm quá tải backend với hàng ngàn request rác và gây lãng phí truy vấn database không cần thiết.

Trong bài viết này, chúng ta sẽ phân tích lý do tại sao short polling thất bại khi mở rộng hệ thống, tại sao WebSocket có thể quá dư thừa cho nhu cầu đẩy dữ liệu một chiều, và cách giải quyết triệt để bằng Server-Sent Events (SSE).

Chi Phí Ẩn Của Polling Ngây Thơ

HTTP short polling hoạt động theo cơ chế rất đơn giản: client liên tục hỏi server "Đã có dữ liệu mới chưa?" sau một khoảng thời gian cố định, ví dụ mỗi 3 giây.

// Một đoạn code polling phổ biến
setInterval(async () => {
  const response = await fetch('/api/orders/123/status');
  const data = await response.json();
  updateUI(data);
}, 3000);

Mặc dù cực kỳ đơn giản về mặt tư duy, mô hình này gây ra những lãng phí nghiêm trọng về mặt kiến trúc:

  1. Overhead Kết Nối Quá Phức Tạp: Mỗi request HTTP đều tiêu tốn chi phí bắt tay TCP, đàm phán bảo mật TLS và truyền tải chuỗi HTTP header. Nếu bạn có 1.000 người dùng online cùng lúc và poll mỗi 3 giây, server phải xử lý hơn 330 request/giây—ngay cả khi trạng thái dữ liệu không hề thay đổi.
  2. Quá Tải Database Nền: Hầu hết request gửi lên đều kích hoạt lệnh query database hoặc kiểm tra cache để xác nhận trạng thái, làm cạn kiệt connection pool và lãng phí CPU server.
  3. Hao Pin Và Đốn Dung Lượng Network: Trên thiết bị di động, việc bật nhả antenna mạng liên tục để gửi request background sẽ làm thiết bị nóng lên, ngốn pin và ngốn dung lượng dữ liệu của người dùng.

Một số team cố gắng khắc phục bằng HTTP long polling—tức là server giữ request lại cho đến khi có dữ liệu mới rồi mới phản hồi. Tuy nhiên, long polling vẫn phải ngắt và tạo lại kết nối HTTP mới cho mỗi event, khiến logic quản lý kết nối ở cả hai phía frontend và backend trở nên cồng kềnh.

WebSocket Có Phải Là Lựa Chọn Duy Nhất?

Khi polling gặp bế tắc, nhiều lập trình viên lập tức chuyển sang WebSocket. WebSocket tạo ra một kết nối TCP hai chiều (bi-directional), cho phép client và server chủ động gửi tin nhắn cho nhau bất kỳ lúc nào.

WebSocket là công cụ tuyệt vời và bắt buộc cho các ứng dụng tương tác hai chiều liên tục như ứng dụng chat, game nhiều người chơi hoặc các công cụ chỉnh sửa tài liệu đồng thời (như Figma, Google Docs).

Tuy nhiên, nếu bạn chỉ sử dụng WebSocket cho nhu cầu duy nhất là server đẩy thông báo/trạng thái xuống client, bạn đang đưa vào hệ thống những sự phức tạp không cần thiết:

  • Bỏ Qua Hạ Tầng HTTP Chuẩn: WebSocket phải trải qua bước handshake nâng cấp protocol (từ HTTP sang WS), làm mất đi các tính năng có sẵn của HTTP như caching, nén dữ liệu header và routing tiêu chuẩn.
  • Phức Tạp Hóa Hạ Tầng: Để mở rộng hệ thống WebSocket ra nhiều node server (cluster), bạn phải thiết lập sticky session, sử dụng Redis Pub/Sub backplane hoặc dựng các socket gateway riêng biệt.
  • Vấn Đề Với Proxy Và Firewall: Các proxy doanh nghiệp hoặc firewall thường tự động ngắt các kết nối WebSocket vô danh hoặc làm gián đoạn kết nối dài.

Nếu ứng dụng chỉ cần nhận dữ liệu từ server mà không cần gửi liên tục tin nhắn ngược lại, WebSocket mang đến nhiều gánh nặng kiến trúc hơn là lợi ích thực tế.

Server-Sent Events (SSE) Là Gì?

Server-Sent Events (SSE) là một chuẩn API có sẵn trên trình duyệt, cho phép server chủ động đẩy dữ liệu real-time xuống client thông qua một kết nối HTTP duy nhất và duy trì lâu dài.

Điểm khác biệt lớn nhất là SSE chỉ hoạt động theo một chiều (unidirectional): dữ liệu chỉ đi từ server về client. Client khởi tạo một request HTTP GET bình thường với header Accept: text/event-stream. Server tiếp nhận và giữ kết nối mở, liên tục stream các dòng dữ liệu dạng text đã được định dạng mỗi khi có sự kiện mới.

Những Ưu Điểm Nổi Bật Của SSE

  • Chạy Trên Hạ Tầng HTTP Native: Sử dụng chuẩn HTTP/1.1 hoặc HTTP/2 mà không cần nâng cấp protocol phức tạp.
  • Tự Động Kết Nối Lại (Auto-Reconnect): Trình duyệt tự động phát hiện khi mất kết nối và thực hiện kết nối lại, kèm theo header Last-Event-ID để server gửi tiếp các sự kiện bị bỏ lỡ.
  • Tích Hợp Định Dạng Event Và ID: Hỗ trợ phân loại sự kiện (custom event type) và ID sự kiện ngay trong chuẩn truyền tải.
  • Dễ Dàng Tương Thích Hạ Tầng: Hoạt động trơn tru với các bộ load balancer, reverse proxy (Nginx, HAProxy) và middleware authentication sẵn có.

Ví Dụ Triển Khai Thực Tế

Hãy cùng xem cách triển khai SSE cực kỳ ngắn gọn với Node.js/Express ở backend và JavaScript thuần ở frontend.

Triển Khai Ở Backend

Phía server chỉ cần cấu hình đúng header phản hồi và giữ kết nối không bị ngắt:

// server.js (Ví dụ với Express)
app.get('/api/live-status', (req, res) => {
  // Cấu hình header bắt buộc cho SSE
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Gửi tin nhắn xác nhận ban đầu
  res.write('data: {"message": "Kết nối thành công"}\n\n');

  // Gửi cập nhật định kỳ khi có dữ liệu mới
  const intervalId = setInterval(() => {
    const payload = JSON.stringify({ timestamp: Date.now(), status: 'active' });
    res.write(`data: ${payload}\n\n`);
  }, 5000);

  // Dọn dẹp tài nguyên khi client ngắt kết nối
  req.on('close', () => {
    clearInterval(intervalId);
    res.end();
  });
});

Lưu ý định dạng dữ liệu của SSE: mỗi tin nhắn bắt đầu bằng tiền tố data: và kết thúc bằng hai ký tự xuống dòng (\n\n).

Triển Khai Ở Frontend

Tại trình duyệt, bạn chỉ cần vài dòng code để lắng nghe stream bằng API EventSource tích hợp sẵn:

// client.js
const eventSource = new EventSource('/api/live-status');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Nhận cập nhật real-time:', data);
  updateDashboardUI(data);
};

eventSource.onerror = (error) => {
  console.error('Lỗi kết nối SSE:', error);
  // Trình duyệt sẽ tự động thử kết nối lại
};

Nếu hệ thống yêu cầu truyền header custom (như Bearer Token authentication), bạn có thể kết hợp fetch API với ReadableStream ở phía frontend thay cho EventSource mặc định.

Những Lưu Ý Quan Trọng Khi Sử Dụng SSE

Mặc dù SSE rất tinh gọn, bạn cần lưu ý một số điểm kỹ thuật sau để triển khai hiệu quả trên môi trường production:

  1. Giới Hạn Kết Nối Trên HTTP/1.1: Các trình duyệt thường giới hạn tối đa 6 kết nối HTTP concurrent cho mỗi domain trên HTTP/1.1. Nếu người dùng mở nhiều tab, kết nối SSE có thể bị nghẽn. Giải pháp: Triển khai SSE trên HTTP/2, vốn hỗ trợ multiplexing hàng trăm kết nối trên một socket duy nhất.
  2. Cơ Chế Heartbeat Tránh Timeout: Các proxy hoặc load balancer (như Nginx, Cloudflare) có thể tự động ngắt kết nối HTTP vô danh nếu không có dữ liệu truyền qua sau 30-60 giây. Hãy gửi thêm gói tin ping (ví dụ : ping\n\n) mỗi 15-20 giây để giữ kết nối luôn sống.
  3. Giải Phóng Tài Nguyên (Graceful Cleanup): Luôn bắt sự kiện close trên request phía server để hủy các timer, xóa subscriber trong pub/sub channel, tránh rò rỉ bộ nhớ (memory leak).

Lời Kết

HTTP polling là một giải pháp tình thế nhanh gọn ban đầu nhưng dễ biến thành điểm nghẽn hệ thống khi mở rộng. Trong khi đó, WebSocket rất mạnh mẽ cho truyền thông hai chiều nhưng mang lại sự phức tạp không cần thiết nếu server chỉ cần gửi dữ liệu một chiều.

Với các tính năng như thông báo real-time, theo dõi trạng thái tiến trình hay bảng dữ liệu biến động, Server-Sent Events chính là điểm cân bằng hoàn hảo: chuẩn HTTP native, tiết kiệm tài nguyên, tự động khôi phục kết nối và cực kỳ dễ bảo trì.

Lần tới khi bạn định gõ setInterval để fetch API định kỳ, hãy cân nhắc chuyển sang SSE. Cả hệ thống server lẫn trải nghiệm người dùng của bạn sẽ được nâng cấp rõ rệt.

GENERATED · REVIEWED BY PKN · 2026-08-12

0

Kết nối

04

Phản hồi

Đang tải bình luận…