Ngừng Polling: Dùng Server-Sent Events Cho App Real-Time

Ngừng Polling: Dùng Server-Sent Events Cho App Real-Time

Cần tính năng real-time cho web? Đừng lạm dụng WebSockets phức tạp hay polling kém hiệu quả. Khám phá sức mạnh của Server-Sent Events (SSE) ngay.

Chắc hẳn bạn từng gặp tình huống này: anh PM (Product Manager) yêu cầu làm một cái dashboard cập nhật real-time. Phản xạ đầu tiên của bạn thường là: dùng setInterval để liên tục "hỏi thăm" server, hoặc lao vào thế giới phức tạp của WebSockets.

Nhưng có một giải pháp thứ ba, tích hợp sẵn trong trình duyệt, sinh ra chính xác cho bài toán này. Nó không đòi hỏi thư viện cồng kềnh, dễ dàng scale qua HTTP tiêu chuẩn, và có sẵn cơ chế tự động kết nối lại (auto-reconnect).

Chào mừng bạn đến với Server-Sent Events (SSE).

Vấn đề của Polling và WebSockets

Trước khi đi sâu vào giải pháp, hãy xem lại tại sao các phản xạ mặc định thường dẫn đến kiến trúc tồi tệ.

HTTP Polling: Giải pháp kém hiệu quả

Polling là cách tiếp cận ngây ngô nhất. Bạn viết setInterval trên frontend để gửi HTTP request tới REST API mỗi 5 giây.

Dù dễ code, nhưng cách này rất kém hiệu quả. Nếu không có đơn mới trong một tiếng, client vẫn gửi 720 requests vô nghĩa, ngốn tài nguyên server, hao pin và gây nghẽn mạng. Về cơ bản, bạn đang tự triển khai một đợt DDoS vào chính backend của mình.

WebSockets: Cỗ máy cồng kềnh

WebSockets cực kỳ tuyệt vời cho game multiplayer hay ứng dụng chat. Nó cung cấp kênh giao tiếp hai chiều (bi-directional) toàn thời gian thực.

Tuy nhiên, nó đi kèm một mớ rắc rối vận hành:

  1. Lưu trạng thái: Load balancer phải cấu hình để xử lý kết nối TCP sống dai và sticky sessions.
  2. Hạ tầng: Routing HTTP thông thường không áp dụng được, thường cần server WebSocket chuyên dụng.
  3. Kết nối lại: API trình duyệt không tự động xử lý rớt mạng. Bạn phải tự viết logic retry hoặc dùng các thư viện nặng như Socket.io.

Thực tế, 90% tính năng "real-time" ở web thông thường chỉ là một chiều (server đẩy data). Client không cần liên tục stream dữ liệu ngược lại. Dùng WebSockets cho việc này chẳng khác nào "dao mổ trâu giết gà".

Giải pháp: Server-Sent Events (SSE)

Máy chủ quá tải với nhiều kết nối

Server-Sent Events cho phép server đẩy dữ liệu real-time tới trình duyệt qua một kết nối HTTP duy nhất.

Bản chất SSE là một HTTP request thông thường, nhưng server trả về header Content-Type: text/event-stream và giữ kết nối mở. Đây là lựa chọn tối ưu cho dữ liệu một chiều:

  • Vẫn là HTTP: Hoạt động mượt mà với load balancers và reverse proxies (Nginx) hiện tại.
  • Tự động Reconnect: API EventSource của trình duyệt tự xử lý rớt mạng.
  • Quản lý Event IDs: Hỗ trợ Last-Event-ID để server có thể "phát lại" sự kiện bị lỡ.
  • Nhẹ nhàng: Không cần cài đặt npm package nặng nề nào.

Xây dựng tính năng Live Feed với SSE

Luồng dữ liệu chảy mượt mà theo một chiều

Hãy cùng xem việc triển khai SSE đơn giản đến mức nào với Node.js, Express và TypeScript.

Phía Backend (Express.js)

Để biến một endpoint Express thành một SSE stream, ta chỉ cần set vài header đặc biệt và trả data về theo định dạng text (data: payload\n\n).

import express, { Request, Response } from 'express';

const app = express();

app.get('/api/live-updates', (req: Request, res: Response) => {
  // 1. Set headers để thiết lập kết nối SSE
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Gửi một tin nhắn khởi tạo
  res.write('data: {"status": "connected"}\n\n');

  // 2. Giả lập việc server đẩy data bất đồng bộ
  const intervalId = setInterval(() => {
    const payload = JSON.stringify({ 
      time: new Date().toISOString(), 
      activeUsers: Math.floor(Math.random() * 1000) 
    });
    
    // Bạn có thể định nghĩa các tên event tuỳ ý!
    res.write('event: metricsUpdate\n');
    res.write(`data: ${payload}\n\n`);
  }, 3000);

  // 3. Quan trọng: Dọn dẹp khi client đóng tab
  req.on('close', () => {
    clearInterval(intervalId);
    res.end();
  });
});

app.listen(3000, () => console.log('SSE Server running on port 3000'));

Chú ý bước dọn dẹp. Nếu quên req.on('close'), server của bạn sẽ bị rò rỉ bộ nhớ (memory leak) vì tiếp tục chạy interval cho các user đã đóng tab.

Phía Frontend (Vanilla JS)

Ở phía client, chúng ta dùng interface EventSource gốc của trình duyệt.

// 1. Mở kết nối tới server
const eventSource = new EventSource('/api/live-updates');

// 2. Lắng nghe các tin nhắn không định danh
eventSource.onmessage = (event) => {
  console.log('Nhận data chung:', event.data);
};

// 3. Lắng nghe event 'metricsUpdate' do ta tự định nghĩa
eventSource.addEventListener('metricsUpdate', (event) => {
  const data = JSON.parse(event.data);
  console.log('Số lượng user đang active:', data.activeUsers);
});

// 4. Xử lý lỗi (Browser sẽ tự động reconnect!)
eventSource.onerror = (error) => {
  console.error('Lỗi SSE, đang thử kết nối lại...', error);
};

// Để chủ động ngắt stream (khi component unmount):
// eventSource.close();

Nếu dùng React useEffect hoặc Vue onMounted, bạn chỉ việc khởi tạo EventSource và nhớ gọi eventSource.close() trong hàm cleanup để tránh các kết nối "ma".

Những hạn chế cần lưu ý

Dù SSE rất tuyệt, bạn cũng cần nắm rõ giới hạn của nó:

  1. Chỉ giao tiếp một chiều: Nếu client cần stream data ngược lại liên tục (như chat), hãy dùng WebSockets. Có thể kết hợp SSE và POST request thông thường cho các action nhỏ, nhưng với dữ liệu hai chiều liên tục, WebSockets vẫn tối ưu hơn.
  2. Giới hạn kết nối HTTP/1.1: Trình duyệt giới hạn tối đa 6 kết nối HTTP/1.1 đồng thời tới cùng một domain. Nếu mở 7 tab dashboard SSE trên HTTP/1.1, tab thứ 7 sẽ bị treo. Nhưng nếu server dùng HTTP/2, giới hạn này bị xoá bỏ hoàn toàn nhờ multiplexing.
  3. Dữ liệu nhị phân: SSE dùng chuẩn UTF-8. Nếu stream binary thô, WebSockets sẽ tốt hơn.

Tổng kết

Lần tới, khi làm dashboard, chuông thông báo hay bản đồ theo dõi đơn hàng, hãy tự hỏi: Client có cần "nói chuyện" liên tục không, hay chỉ cần "lắng nghe"?

Nếu chỉ lắng nghe, hãy dẹp WebSockets cồng kềnh sang một bên. Bỏ ngay thói quen setInterval polling. Hãy dùng Server-Sent Events. Đó là tiêu chuẩn mạnh mẽ, đáng tin cậy và cực kỳ đơn giản để xây dựng tính năng real-time. Giữ cho hệ thống tinh gọn, và để trình duyệt lo những phần việc nặng nhọc.

NT

viết bởi

Nguyên Tech

0

Phản hồi

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

Lattice.

Một không gian để viết dài, đọc chậm, và trò chuyện thật — không thuật toán, không quảng cáo.

© 2026 · Lattice · Đà Nẵng (16°03′ N, 108°12′ E) · v0.1 · system + ink + indigo