Người ta hay chọn WebSocket vì nghĩ nó "nhẹ hơn HTTP". Phần trước đo WebSocket; bài này đo SSE trên cùng máy chủ, cùng số client, và so hai bảng.

SSE trong Vert.x là ba dòng

r.get("/sse").handler(c -> {
    var res = c.response();
    res.setChunked(true)
       .putHeader("Content-Type", "text/event-stream")
       .putHeader("Cache-Control", "no-cache");
    SSE.add(res);
    res.closeHandler(v -> SSE.remove(res));
});

// gui mot su kien
res.write("data: " + json + "\n\n");

Không có bắt tay nâng cấp, không có giao thức riêng. Đây là một response HTTP bình thường không bao giờ kết thúc, và trình duyệt có sẵn EventSource để đọc nó.

Đo cạnh nhau

Cùng máy chủ, cùng máy, mỗi lần chạy trên tiến trình sạch:

Số kết nối Heap mỗi kết nối RSS Số luồng Phát tin cho tất cả
1 000 WebSocket 4 263 B 120 MiB 37 7,5 ms
SSE 4 071 B 109 MiB 37 9,1 ms
5 000 WebSocket 4 199 B 150 MiB 39 17,9 ms
SSE 3 909 B 172 MiB 37 24,5 ms
10 000 WebSocket 4 037 B 177 MiB 39 21,3 ms
SSE 3 905 B 203 MiB 39 54,9 ms
Bộ nhớ mỗi kết nối gần như bằng nhau — khoảng 4 KB cho cả hai. Điều đó hợp lý khi nghĩ kỹ: dù là WebSocket hay SSE, thứ máy chủ giữ vẫn là một kết nối TCP cộng một chút trạng thái. Giao thức bên trên gần như không đổi chi phí đó.

Nên nếu bạn đang chọn WebSocket vì "nó nhẹ hơn", bảng trên nói rằng lý do đó không tồn tại.

Chỗ khác biệt thật

Phát tin ở quy mô lớn. Ở mười nghìn kết nối, WebSocket mất 21,3 ms còn SSE mất 54,9 ms — chậm hơn 2,5 lần. Lý do nằm ở khung dữ liệu: SSE gửi văn bản có cấu trúc (data: ...\n\n) qua chunked encoding, còn WebSocket gửi khung nhị phân gọn hơn. Ở một nghìn kết nối thì chênh lệch không đáng kể; ở trăm nghìn thì đáng.

Một chiều hay hai chiều. Đây là khác biệt quyết định. SSE chỉ đi từ máy chủ xuống client. Client muốn gửi gì thì phải mở một request HTTP riêng — vẫn được, nhưng là hai đường thay vì một.

Tự kết nối lại. EventSource của trình duyệt tự động kết nối lại khi đứt, và gửi kèm header Last-Event-ID để máy chủ biết gửi tiếp từ đâu. Với WebSocket bạn phải tự viết phần này, và tự viết phần này đúng thì khó hơn nó trông.

Đi qua hạ tầng cũ. SSE là HTTP thường, nên proxy, cân bằng tải, tường lửa doanh nghiệp đều hiểu. WebSocket cần nâng cấp giao thức, và không ít thiết bị trung gian vẫn cắt nó.

Chọn cái nào

Bài toán Nên dùng
Bảng điều khiển, thông báo, giá cổ phiếu, tiến độ tác vụ SSE
Chat, game, cộng tác thời gian thực, gõ phím trực tiếp WebSocket
Cần chạy qua proxy doanh nghiệp không kiểm soát được SSE
Trăm nghìn kết nối trở lên, mỗi giây một lần phát WebSocket

Luật rút gọn: cần hai chiều thật thì WebSocket, còn lại thì SSE — vì nó đơn giản hơn, tự kết nối lại, và đo ra không tốn hơn.

Một cái bẫy chung

Cả hai đều giữ tham chiếu tới kết nối trong một tập, và cả hai đều cần dọn:

res.closeHandler(v -> SSE.remove(res));      // SSE
ws.closeHandler(v -> WS.remove(ws));         // WebSocket

Thiếu dòng này thì tập chỉ lớn lên, và vòng lặp phát tin ngày càng chậm vì nó đang gửi cho những kết nối đã chết từ lâu. Với 4 KB mỗi kết nối, một dịch vụ chạy vài tuần sẽ tự giải thích vì sao nó hết bộ nhớ.

Bài sau: xác thực và phân quyền trong Vert.x Web, và chi phí kiểm JWT trên mỗi request.

Thử ba mươi giây

# xem mot luong SSE bang mat
curl -N -H "Accept: text/event-stream" http://localhost:8080/sse

Cờ -N tắt bộ đệm của curl. Nếu không thấy gì cho tới khi ngắt, máy chủ của bạn đang gom thân response thay vì gửi từng đoạn — thiếu setChunked(true) hoặc có một proxy ở giữa đang đệm lại.