Hướng dẫn Nginx Load Balancing cho Node.js chi tiết nhất

VMas-Dev-KA

Nginx Load Balancing: Cân bằng tải cho hệ thống Node.js trong 10 phút

Nếu ứng dụng Node.js của bạn bắt đầu quá tải, đã đến lúc nghĩ đến việc scale ngang (thêm nhiều instance thay vì nâng cấp server). Và để phân phối đều request đến các instance đó, bạn cần một Load Balancer. Nginx là lựa chọn hàng đầu nhờ hiệu năng cao, cấu hình đơn giản và hoàn toàn miễn phí (bản Open Source).

Bài viết này hướng dẫn bạn cấu hình Nginx Load Balancing cho hệ thống Node.js chỉ trong 10 phút, giải quyết triệt để bài toán giữ session đăng nhập khi user nhảy server.

Lưu ý: Nếu bạn chưa biết cách cài đặt Nginx làm reverse proxy cho một Node.js đơn lẻ, hãy xem lại bài “Cấu hình Nginx Reverse Proxy” trước khi bắt đầu.


Khi nào bạn cần Load Balancing?

Kiến trúc Nginx load balancing cho 3 Node.js instance

Load Balancing không phải thứ bạn cài đặt ngay từ ngày đầu. Bạn cần nó khi:

  • CPU hoặc RAM của server liên tục > 80% trong giờ cao điểm.
  • Số lượng kết nối đồng thời (concurrent connections) vượt ngưỡng mà Node.js (single-thread) có thể xử lý mượt.
  • Bạn muốn hệ thống High Availability – nếu một instance Node.js chết, traffic vẫn được chuyển sang instance khác.
  • Bạn cần bảo trì (update code) mà không làm gián đoạn người dùng – dùng kỹ thuật “rolling update” qua load balancer.

Tóm lại: Load Balancer đứng trước nhiều instance Node.js, giống như một “lễ tân” thông minh, điều phối khách (request) đến các phòng (instance) rảnh nhất.


Cấu trúc Upstream trong Nginx

Trong Nginx, khối upstream dùng để định nghĩa một nhóm các server backend (Node.js instance). Đây là trái tim của Load Balancing.

Cú pháp cơ bản

Mở file cấu hình Nginx (thường là /etc/nginx/nginx.conf hoặc /etc/nginx/sites-available/example.com), thêm khối này bên ngoài khối server:

upstream nodejs_cluster {
    # Các Node.js instance chạy trên cùng máy hoặc khác máy
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://nodejs_cluster;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Giải thích:

  • upstream nodejs_cluster – tên cluster bạn tự đặt.
  • server 127.0.0.1:3001 – mỗi dòng là một backend. Có thể là IP/Domain + port. Ở đây giả sử 3 instance Node.js chạy local ở cổng 3001, 3002, 3003.
  • proxy_pass http://nodejs_cluster – thay vì trỏ proxy_pass đến một URL cụ thể, bạn trỏ đến tên upstream vừa tạo.

Sau khi lưu file, kiểm tra và reload Nginx:

nginx -t
systemctl reload nginx

Lúc này Nginx đã phân phối request theo thuật toán mặc định Round Robin.


Các thuật toán cân bằng tải phổ biến

So sánh thuật toán round robin và ip_hash trong nginx

Dựa theo tài liệu chính thức của Nginx, dưới đây là 3 thuật toán thường dùng nhất với bản Open Source.

1. Round Robin (mặc định)

Mỗi request luân phiên gửi đến từng server theo vòng tròn. Ví dụ: request 1 → server1, request 2 → server2, request 3 → server3, request 4 → server1…

Cấu hình: (không cần thêm directive nào)

upstream nodejs_cluster {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

2. Least Connections (ít kết nối nhất)

Request được gửi đến server đang có ít kết nối đang xử lý nhất. Phù hợp khi mỗi request có thời gian xử lý khác nhau.

upstream nodejs_cluster {
    least_conn;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

3. IP Hash – Giải pháp cho Sticky Sessions

Đây là phần quan trọng nhất nếu ứng dụng Node.js của bạn lưu session ở memory (như express-session mặc định dùng MemoryStore) hoặc login state dựa trên session cookie.

Với Round Robin, user đăng nhập ở instance 3001, request tiếp theo lại rơi vào 3002 → bị mất login. IP Hash giải quyết bằng cách: tính hash từ địa chỉ IP của client, đảm bảo client luôn được gửi đến cùng một backend (trừ khi backend đó chết).

upstream nodejs_cluster {
    ip_hash;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

Lưu ý: Nếu thêm hoặc bớt server, IP Hash có thể thay đổi ánh xạ, khiến user bị redirect đến server khác. Giải pháp tốt hơn cho production là dùng Redis hoặc JWT stateless thay vì session memory. Nhưng nếu chưa thể thay đổi kiến trúc, ip_hash là cứu cánh.

⚠️ Cảnh báo: Nginx Plus (bản trả phí) hỗ trợ sticky cookie chính xác hơn IP Hash. Bản Open Source không có sticky session dựa trên cookie. Hãy cân nhắc.


Thực hành: Cấu hình Nginx Load Balancer cho 3 App Node.js

Bây giờ, chúng ta sẽ đi từ A đến Z: chạy 3 instance Node.js đơn giản và cấu hình Nginx load balancing.

Bước 1: Tạo 3 file Node.js app đơn giản

Tạo thư mục load-balance-demo, bên trong tạo 3 file:

app1.js

const express = require('express');
const app = express();
const port = 3001;

app.get('/', (req, res) => {
    res.send(`Hello from Node.js instance 1 (PID: ${process.pid})`);
});

app.listen(port, () => {
    console.log(`App1 running on http://localhost:${port}`);
});

app2.js – tương tự, đổi port 3002 và text “instance 2”.

app3.js – tương tự, đổi port 3003 và text “instance 3”.

Chạy 3 instance trong 3 terminal riêng:

node app1.js
node app2.js
node app3.js

Bước 2: Cấu hình Nginx upstream (có ip_hash)

Sửa file cấu hình Nginx:

upstream nodejs_cluster {
    ip_hash;  # giữ session theo IP
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

server {
    listen 80;
    server_name lab.example.com;  # dùng tên miền hoặc localhost để test

    location / {
        proxy_pass http://nodejs_cluster;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache_bypass $http_upgrade;
    }
}

Proxy WebSocket? Các dòng UpgradeConnection giúp hỗ trợ WebSocket nếu app Node.js của bạn dùng socket.io.

Bước 3: Reload Nginx và test

nginx -t && systemctl reload nginx

Mở trình duyệt gõ http://lab.example.com (hoặc http://localhost nếu bạn dùng server_name localhost). Refresh nhiều lần, bạn sẽ luôn thấy cùng một instance (nhờ ip_hash). Nếu bạn comment ip_hash đi, mỗi lần refresh sẽ luân phiên 3 instance.

Bước 4: Mô phỏng một instance bị chết

Dừng app2 (Ctrl+C). Gửi request liên tục, bạn sẽ thấy Nginx vẫn gửi request đến instance đã chết (vì Nginx Open Source mặc định không có active health check). Sau khoảng 2-3 lần thất bại, Nginx sẽ tự động loại instance đó khỏi vòng quay nếu bạn cấu hình max_failsfail_timeout.

Thêm vào block server trong upstream:

server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3003 max_fails=3 fail_timeout=30s;
  • max_fails=3 – sau 3 lần thất bại (kết nối timeout hoặc lỗi HTTP 5xx), Nginx cho rằng server chết.
  • fail_timeout=30s – trong 30 giây tiếp theo, Nginx không gửi request đến server đó. Sau 30s, thử lại.

Đây là cơ chế passive health check – chỉ phát hiện lỗi khi có request thực tế. Nginx Plus hỗ trợ active health check (gửi request kiểm tra định kỳ).


Lỗi thường gặp và cách khắc phục

Lỗi nginx không phát hiện nodejs instance đã chết khi thiếu max_fails

Lỗi 1: Server die nhưng Nginx vẫn gửi traffic tới

Nguyên nhân: Không cấu hình max_failsfail_timeout. Cách sửa: Như đã hướng dẫn ở trên. Ngoài ra, bạn có thể dùng proxy_next_upstream để chỉ định trường hợp nào Nginx thử server tiếp theo.

location / {
    proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
    proxy_pass http://nodejs_cluster;
}

Lỗi 2: User bị đăng xuất liên tục

Nguyên nhân: Không dùng ip_hash hoặc sticky sessions, session lưu trong RAM. Cách sửa: Chuyển session sang Redis hoặc dùng ip_hash. Nếu dùng ip_hash mà vẫn bị logout, kiểm tra xem header X-Real-IP có gửi đúng IP client không.

Lỗi 3: WebSocket không hoạt động qua load balancer

Nguyên nhân: Thiếu các header UpgradeConnection. Cách sửa: Thêm các dòng proxy_set_header như trong ví dụ thực hành.


Best Practices khi dùng Nginx Load Balancer cho Node.js

  1. Luôn dùng HTTPS ở tầng Load Balancer
    Cấu hình SSL trên Nginx, để Nginx xử lý mã hóa/giải mã, Node.js chỉ nhận HTTP plaintext → giảm tải CPU cho app.

  2. Sử dụng Docker để quản lý các instance app dễ dàng hơn
    Docker Compose giúp bạn scale nhanh: docker-compose up --scale app=3. Kết hợp với Nginx sử dụng DNS resolver động (cần cấu hình resolver và biến trong proxy_pass).

  3. Đặt keepalive để tái sử dụng kết nối đến backend

    upstream nodejs_cluster {
        keepalive 32;
        server 127.0.0.1:3001;
    }

    Kết hợp với proxy_http_version 1.1proxy_set_header Connection "" để giảm overhead kết nối TCP.

  4. Giám sát upstream với stub_status module

    location /nginx_status {
        stub_status;
        allow 127.0.0.1;
        deny all;
    }
  5. Dùng log để theo dõi upstream nào xử lý request
    Thêm $upstream_addr vào log_format:

    log_format main '$remote_addr - $upstream_addr - $request';

FAQ

Load Balancer có làm app chậm đi không?

Có một chút (thường < 5ms latency) vì Nginx phải xử lý và chuyển tiếp request. Tuy nhiên lợi ích từ việc phân tán tải lớn hơn nhiều so với độ trễ nhỏ này. Nginx được viết bằng C, rất nhẹ.

Cần ít nhất bao nhiêu server để chạy load balancing?

Bạn có thể chạy Nginx và 2+ Node.js instance trên cùng một máy chủ (dùng các port khác nhau). Về mặt kỹ thuật, ít nhất 2 backend để thấy hiệu quả phân tải. Tuy nhiên nếu muốn High Availability cho chính Nginx, bạn cần 2 máy chủ riêng cho Nginx kết hợp với Keepalived hoặc cân bằng tải DNS.

Nginx Open Source có health check không?

Có nhưng chỉ là passive (phát hiện qua lỗi request thực). Nginx Plus có active health check (chủ động ping). Với bản Open Source, bạn có thể dùng thêm công cụ như consul hoặc aws alb để thay thế.


Kết luận

Chỉ với vài dòng cấu hình upstreamproxy_pass, bạn đã biến Nginx thành một Load Balancer mạnh mẽ cho hệ thống Node.js. Từ Round Robin đơn giản đến ip_hash xử lý session, từ passive health check đến các best practices, bài viết đã trang bị cho bạn đủ kiến thức để scale ứng dụng ngay hôm nay.

Đừng quên luôn kiểm tra log và giám sát sau khi triển khai. Nếu bạn mới bắt đầu với Nginx, hãy xem lại bài “Cấu hình Nginx Reverse Proxy” để nắm vững nền tảng trước khi load balancing.

Xem lại bài: Cấu hình Nginx Reverse Proxy

Chia sẻ bài viết này