Khi Load Balancing không chỉ dành cho Web
Trước đây, mình từng gặp phen hú vía khi hệ thống quản lý cho 50 nhân viên bỗng dưng nghẽn cổ chai. Lúc đó, mình chỉ coi Nginx là Web Server thuần túy để chạy PHP hay Node.js.
Cứ hễ nhắc đến cân bằng tải (Load Balancing – LB) cho Database hay Redis là mình lại lạch cạch cài HAProxy. Tuy nhiên, việc vận hành quá nhiều công cụ khác nhau khiến việc debug trở thành ác mộng mỗi khi có sự cố. Sau đó, mình tìm thấy Nginx Stream Module — một tính năng cực kỳ mạnh mẽ giúp xử lý kết nối ở tầng Transport (Layer 4).
Nếu bạn đang đau đầu vì cụm MySQL quá tải hoặc cần một điểm truy cập duy nhất (Single Entry Point) cho hàng ngàn thiết bị IoT kết nối vào MQTT Broker, Nginx Stream chính là giải pháp tiết kiệm và hiệu quả nhất. Thay vì cài thêm phần mềm mới, bạn hãy tận dụng ngay instance Nginx hiện có để điều phối lưu lượng cho Database hoặc Redis. Cách tiếp cận này giúp giảm độ phức tạp cho hạ tầng đáng kể.
Kiểm tra và kích hoạt Nginx Stream Module
Tin mình đi, đừng vội vàng copy cấu hình vào rồi chạy ngay. Không phải bản Nginx nào cũng cài sẵn Stream Module. Các bản build mặc định trên Ubuntu thường tách module này ra để giữ cho core Nginx nhẹ nhất có thể. Để biết hệ thống đã sẵn sàng chưa, hãy gõ lệnh:
nginx -V 2>&1 | grep --color -o with-stream
Nếu màn hình trống trơn, nghĩa là bạn thiếu module --with-stream. Trên môi trường Ubuntu/Debian, việc bổ sung rất đơn giản:
sudo apt update && sudo apt install libnginx-mod-stream -y
Lưu ý quan trọng: Hãy mở file /etc/nginx/nginx.conf và kiểm tra xem có dòng include /etc/nginx/modules-enabled/*.conf; ở ngay đầu file hay chưa. Nếu thiếu dòng này, Nginx sẽ không thèm ngó ngàng tới các module bạn vừa cài đâu.
Cấu hình chuẩn: Đừng nhầm lẫn với HTTP
Đây là lỗi “kinh điển” khiến anh em IT tốn cả buổi tối để fix. Cấu hình web thông thường nằm trong block http { ... }. Tuy nhiên, cấu hình cho TCP/UDP bắt buộc phải nằm độc lập, ngang hàng với block http. Cấu trúc chuẩn sẽ như sau:
user www-data;
worker_processes auto;
events {
worker_connections 1024;
}
# Khu vực dành riêng cho TCP/UDP
stream {
include /etc/nginx/conf.d/stream/*.conf;
}
http {
# Cấu hình website vẫn nằm ở đây
...
}
Để quản lý chuyên nghiệp, mình luôn tạo thư mục riêng /etc/nginx/conf.d/stream/. Mỗi dịch vụ như MySQL hay Redis sẽ là một file cấu hình riêng biệt trong đó.
Ứng dụng thực tế: MySQL, Redis và MQTT
1. Cân bằng tải cho MySQL Cluster
Giả sử hệ thống của bạn có 2 node MySQL Read-Replica. Mục tiêu là để ứng dụng trỏ vào một IP duy nhất của Nginx tại port 3306. Nginx sẽ dùng thuật toán Least Connections để đẩy traffic vào node đang rảnh nhất.
# File: /etc/nginx/conf.d/stream/mysql.conf
upstream mysql_servers {
least_conn;
server 10.0.0.10:3306 max_fails=3 fail_timeout=30s;
server 10.0.0.11:3306 max_fails=3 fail_timeout=30s;
}
server {
listen 3306;
proxy_pass mysql_servers;
proxy_connect_timeout 5s;
proxy_timeout 60s; # Rất quan trọng cho query nặng
}
Kinh nghiệm xương máu: Đừng để proxy_timeout quá thấp. Với các query báo cáo chạy mất 40-50 giây, nếu bạn để timeout 30s, kết nối sẽ đứt giữa chừng và ứng dụng sẽ báo lỗi liên tục.
2. Dự phòng (Failover) cho Redis
Với Redis dùng làm cache, tốc độ là ưu tiên hàng đầu. Nếu bạn không dùng Sentinel nhưng vẫn muốn có phương án dự phòng, hãy dùng từ khóa backup. Node phụ chỉ hoạt động khi node chính “sập”.
upstream redis_backend {
server 10.0.0.20:6379;
server 10.0.0.21:6379 backup;
}
server {
listen 6379;
proxy_pass redis_backend;
}
3. Điều phối 10.000 thiết bị MQTT cho IoT
MQTT là giao thức dựa trên TCP. Khi số lượng thiết bị cảm biến tăng vọt, một broker duy nhất thường sẽ quá tải CPU. Mình dùng cơ chế hash để đảm bảo một thiết bị luôn kết nối ổn định vào đúng một broker nhất định.
upstream mqtt_cluster {
hash $remote_addr consistent;
server 10.0.0.30:1883;
server 10.0.0.31:1883;
}
server {
listen 1883;
proxy_pass mqtt_cluster;
}
Bảo mật: Chống Brute Force và lộ IP
Khi dùng Nginx làm proxy, các server phía sau sẽ chỉ thấy IP của Nginx. Nếu ứng dụng của bạn yêu cầu IP thật của client để audit, hãy tìm hiểu về Proxy Protocol. Tuy nhiên, hãy nhớ kiểm tra xem MySQL hay Redis phiên bản bạn đang dùng có hỗ trợ giao thức này không nhé.
Để ngăn chặn các cuộc tấn công dò mật khẩu (Brute Force) vào port Database, mình luôn giới hạn số kết nối đồng thời. Ví dụ, mỗi IP chỉ được phép mở tối đa 5 kết nối tới DB:
stream {
limit_conn_zone $binary_remote_addr zone=db_limit:10m;
server {
listen 3306;
limit_conn db_limit 5;
proxy_pass mysql_servers;
}
}
Kiểm tra và Giám sát
Sau khi sửa xong, hãy chạy lệnh nginx -t để kiểm tra cú pháp. Nếu mọi thứ ổn, hãy reload lại dịch vụ bằng lệnh systemctl reload nginx. Để chắc chắn Nginx đã lắng nghe trên port mong muốn, mình thường dùng ss:
ss -tlnp | grep -E '3306|6379|1883'
Mặc định, Nginx Stream khá “kiệm lời” về log. Bạn nên định nghĩa format log riêng để dễ dàng theo dõi lượng bytes gửi/nhận và thời gian session tồn tại:
log_format tcp_stats '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time';
access_log /var/log/nginx/stream_access.log tcp_stats;
Việc làm chủ Nginx Stream giúp mình xử lý gọn gàng các bài toán hạ tầng phức tạp mà không tốn thêm tài nguyên cho phần mềm trung gian. Hy vọng những chia sẻ này sẽ giúp bạn tối ưu hệ thống mượt mà hơn.

