Quay lại danh sách
Tin tức công nghệ

Tối ưu hóa Linux Kernel chuyên sâu: Bí quyết đạt 100.000 kết nối WebSockets đồng thời cho Node.js trên VPS

30 tháng 5, 2026

Đặt vấn đề: Thách thức của bài toán 100k kết nối đồng thời

Trong kỷ nguyên của các ứng dụng thời gian thực (real-time applications) như hệ thống chat, bảng điều khiển tài chính, hay trò chơi trực tuyến, WebSockets đã trở thành giao thức cốt lõi nhờ khả năng giao tiếp hai chiều thấp độ trễ. Tuy nhiên, khi quy mô người dùng tăng trưởng, việc duy trì hàng trăm nghìn kết nối đồng thời (concurrent connections) trên một máy chủ VPS duy nhất là một thử thách không hề nhỏ.

Nhiều kỹ sư hệ thống thường lầm tưởng rằng chỉ cần nâng cấp tài nguyên phần cứng (CPU, RAM) hoặc tối ưu hóa mã nguồn Node.js là đủ. Thực tế, rào cản lớn nhất lại nằm ở cấu hình mặc định của Linux Kernel. Theo cấu hình nguyên bản, Linux được tối ưu hóa cho các tác vụ tính toán tổng hợp hoặc các dịch vụ web truyền thống, nơi các kết nối đóng/mở nhanh chóng, chứ không phải cho hàng trăm nghìn kết nối duy trì liên tục trong thời gian dài. Bài viết này sẽ hướng dẫn bạn từng bước can thiệp chuyên sâu vào hạt nhân Linux để giải phóng toàn bộ sức mạnh phần cứng của VPS.

1. Hiểu về kiến trúc Single-Thread của Node.js và giới hạn của Linux

Node.js hoạt động dựa trên cơ chế đơn luồng (single-thread) kết hợp với Event Loop và mô hình I/O phi chặn (Non-blocking I/O). Kiến trúc này cực kỳ hiệu quả trong việc xử lý hàng nghìn kết nối I/O cùng lúc vì nó không phải tốn tài nguyên tạo luồng (thread) mới cho mỗi kết nối như Apache.

Tuy nhiên, mỗi kết nối WebSocket về bản chất là một kết nối TCP kéo dài (persistent TCP connection). Trong hệ điều hành Linux, mỗi kết nối TCP này được đại diện bởi một File Descriptor (FD) và tiêu tốn một lượng bộ nhớ đệm (socket buffers) nhất định. Nếu không cấu hình lại Kernel, bạn sẽ sớm vấp phải các lỗi kinh điển như EMFILE: too many open files hoặc hệ thống tự động từ chối kết nối mới dù RAM và CPU vẫn còn trống rất nhiều.

2. Tăng giới hạn File Descriptors (Mọi thứ trong Linux đều là File)

Để đạt mục tiêu 100.000 kết nối, hệ thống cần quyền mở ít nhất 100.000 file descriptors cùng lúc (trên thực tế cần cao hơn để dự phòng cho các tiến trình hệ thống khác).

Cấu hình giới hạn toàn hệ thống (System-wide Limits)

Đầu tiên, chúng ta cần tăng giới hạn tối đa số lượng file mà toàn bộ hệ điều hành có thể mở. Chỉnh sửa tệp tin /etc/sysctl.conf và thêm cấu hình sau:

fs.file-max = 2097152

Con số 2.097.152 đảm bảo hệ thống có dư không gian để xử lý lượng kết nối khổng lồ mà không sợ cạn kiệt tài nguyên FD.

Cấu hình giới hạn cho User và Process (Ulimits)

Tiếp theo, bạn cần cấu hình giới hạn cho người dùng chạy ứng dụng Node.js (ví dụ là user deploy hoặc root). Chỉnh sửa tệp tin /etc/security/limits.conf:

  • soft nofile 200000: Giới hạn mềm (người dùng có thể tự tăng lên đến mức giới hạn cứng).
  • hard nofile 200000: Giới hạn cứng tối đa mà hệ thống cho phép đối với user này.

Lưu ý quan trọng: Nếu bạn quản lý dịch vụ Node.js thông qua Systemd, các cấu hình trong limits.conf có thể bị bỏ qua. Bạn cần bổ sung dòng LimitNOFILE=200000 vào file cấu hình dịch vụ .service của Systemd để đảm bảo tiến trình Node.js nhận đúng quyền hạn.

3. Tối ưu hóa Stack TCP/IP của Linux Kernel

Đây là phần quan trọng nhất trong quá trình tối ưu hóa hạt nhân. Chúng ta sẽ điều chỉnh cách Linux quản lý hàng đợi kết nối, thời gian sống của socket và bộ nhớ đệm để phù hợp với đặc thù của WebSockets.

Quản lý hàng đợi kết nối (Connection Backlog)

Khi có hàng nghìn kết nối gửi đến cùng một lúc (thời điểm cao điểm), hệ thống cần một hàng đợi đủ lớn để chứa các kết nối đang chờ xử lý trước khi Node.js kịp chấp nhận (accept) chúng. Thêm các dòng sau vào /etc/sysctl.conf:

net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

Thuộc tính somaxconn kiểm soát kích thước hàng đợi tối đa của socket lắng nghe, trong khi tcp_max_syn_backlog kiểm soát số lượng kết nối dạng "nửa mở" (TCP SYN) mà hệ thống có thể ghi nhớ.

Tối ưu hóa bộ nhớ đệm TCP (TCP Window Sizes & Buffers)

Mỗi kết nối TCP yêu cầu bộ nhớ RAM để làm bộ đệm gửi (send buffer) và nhận (receive buffer). Với 100.000 kết nối, nếu mỗi socket chiếm quá nhiều RAM, VPS của bạn sẽ nhanh chóng rơi vào tình trạng Out of Memory (OOM). Chúng ta cần cấu hình Kernel tự động co giãn bộ nhớ đệm một cách thông minh:

net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wem = 4096 65536 16777216

Ba giá trị lần lượt đại diện cho: Kích thước tối thiểu (Minimum), Kích thước mặc định (Default), và Kích thước tối đa (Maximum) tính bằng bytes. Việc đặt mức tối thiểu thấp (4KB) giúp các kết nối idle (không truyền dữ liệu liên tục) tiêu tốn cực ít RAM, giải phóng tài nguyên để duy trì số lượng kết nối lớn.

Xử lý trạng thái TIME_WAIT và tái sử dụng cổng kết nối

Trong môi trường tần suất kết nối cao, các socket cũ sau khi đóng sẽ rơi vào trạng thái TIME_WAIT trong khoảng 60 giây để đảm bảo các gói tin đi lạc được xử lý hết. Điều này có thể làm cạn kiệt dải cổng (ephemeral ports) của hệ thống. Hãy kích hoạt tính năng tái sử dụng socket:

net.ipv4.tcp_tw_reuse = 1
et.ipv4.ip_local_port_range = 1024 65535

Mở rộng dải cổng ip_local_port_range từ 1024 đến 65535 giúp tăng không gian định tuyến cho các kết nối outbound phát sinh từ ứng dụng.

4. Cơ chế Keepalive và giám sát kết nối "chết"

Các kết nối WebSocket kéo dài thường xuyên đối mặt với nguy cơ bị ngắt giữa chừng do tường lửa, NAT router của nhà mạng hoặc thiết bị của khách hàng mất mạng đột ngột mà không kịp gửi gói tin đóng kết nối (FIN). Nếu Kernel không phát hiện ra, các kết nối "ma" này sẽ tồn tại mãi mãi và chiếm dụng tài nguyên hệ thống.

Hãy cấu hình TCP Keepalive mạnh mẽ hơn để Kernel chủ động kiểm tra trạng thái kết nối:

net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5

Cấu hình trên có nghĩa là: Sau 300 giây (5 phút) không có hoạt động, Kernel sẽ gửi gói tin probe kiểm tra. Cứ mỗi 15 giây gửi lại một lần, nếu sau 5 lần liên tiếp không phản hồi, kết nối đó sẽ chính thức bị hủy bỏ và giải phóng tài nguyên ngay lập tức.

5. Áp dụng cấu hình và kiểm thử hiệu năng (Benchmarking)

Sau khi đã thêm toàn bộ các tham số tối ưu vào tệp tin /etc/sysctl.conf, bạn hãy chạy lệnh sau để các thay đổi có hiệu lực ngay lập tức mà không cần khởi động lại VPS:

sudo sysctl -p

Kiểm thử với k6 hoặc Autocannon

Để chứng minh hệ thống đã sẵn sàng cho cột mốc 100k kết nối, bạn không thể thiếu bước kiểm thử áp lực (Load Testing). Công cụ k6 hoặc Autocannon (viết bằng Node.js) là những lựa chọn tuyệt vời. Bạn nên chuẩn bị ít nhất 2 đến 3 VPS khác đóng vai trò là client để phân phối tải, vì một máy client đơn lẻ thường bị giới hạn phần cứng và băng thông không thể tạo ra 100.000 kết nối đồng thời.

Kết luận

Đạt được cột mốc 100.000 kết nối WebSockets đồng thời trên một máy chủ VPS Node.js hoàn toàn là một mục tiêu khả thi nếu bạn làm chủ được Linux Kernel Tuning. Việc can thiệp sâu vào cấu hình hệ thống từ File Descriptors, TCP Buffers cho đến cơ chế Keepalive không chỉ giúp tối ưu hóa hiệu suất phần cứng cao nhất mà còn giúp doanh nghiệp tiết kiệm đáng kể chi phí hạ tầng Cloud VPS.

Hãy nhớ rằng, tối ưu hóa hệ thống là một quá trình liên tục. Bạn cần kết hợp các công cụ giám sát như Prometheus và Grafana để liên tục theo dõi các chỉ số và đưa ra những điều chỉnh mịn (fine-tuning) phù hợp nhất với hành vi người dùng trên ứng dụng của mình.

Tối ưu hóa Linux Kernel chuyên sâu: Bí quyết đạt 100.000 kết nối WebSockets đồng thời cho Node.js trên VPS | DPTCloud