"Trái tim" của mọi hệ thống Backend: Giải phẫu Nginx, Reverse Proxy và nguyên lý Event-Driven
Chào anh em cộng đồng Viblo!
Ở bài viết trước về PHP-FPM, mình có ví von Nginx giống như một "Anh lễ tân" đứng đón khách, còn PHP-FPM là "Nhà bếp". Lễ tân nhận order, ném vào bếp, chờ thức ăn chín rồi mang ra cho khách.
Nhưng nếu Nginx chỉ làm mỗi việc "chuyển phát nhanh" như vậy, tại sao nó lại được xưng tụng là "Hòn đá tảng" của Internet hiện đại? Tại sao các hệ thống khổng lồ của Netflix, Cloudflare hay Airbnb đều dùng nó?
Hôm nay, chúng ta sẽ lột mặt nạ của Nginx để xem bên dưới lớp vỏ Web Server, con quái vật này hoạt động như thế nào mà có thể gánh được hàng chục ngàn kết nối cùng lúc mà RAM vẫn "mát rượi". Lên xe!
1. Bài toán C10K và Sự sụp đổ của Apache
Để hiểu sự bá đạo của Nginx, ta phải quay về đầu những năm 2000 với bài toán nổi tiếng: C10K (Concurrent 10,000 connections) - Làm sao để server xử lý 10.000 kết nối đồng thời?
Thời điểm đó, Apache là ông vua. Nhưng kiến trúc của Apache là Thread-Driven (Mỗi request là một tiến trình/luồng).
Giống như một nhà hàng: Cứ 1 khách vào, nhà hàng lại phải tuyển riêng 1 người phục vụ đứng trực bên cạnh. Nếu có 10.000 khách, phải có 10.000 nhân viên. Nhân viên thì cần trả lương (RAM) và không gian đứng (CPU). Kết quả: Tràn RAM, server sập!
Năm 2004, Nginx ra đời và giải bài toán C10K bằng kiến trúc Event-Driven (Hướng sự kiện) & Asynchronous (Bất đồng bộ).
Vẫn là nhà hàng đó, nhưng Nginx chỉ dùng 1 người phục vụ (Worker Process) cực kỳ nhanh nhẹn. Khách A gọi món xong, trong lúc chờ bếp nấu, người phục vụ không đứng đợi mà chạy ngay sang bàn khách B, khách C để ghi order. Khi nào bếp báo nấu xong món của A (Event bắn ra), người phục vụ mới quay lại mang đồ cho A.
Nhờ vòng lặp sự kiện (Event Loop) giống hệt Node.js, một Worker của Nginx có thể duy trì hàng ngàn kết nối cùng lúc mà chỉ tốn vài Megabytes RAM!
2. Nginx không chỉ là Web Server, nó là Reverse Proxy
Rất nhiều anh em Fresher lầm tưởng Nginx chỉ dùng để... chạy web PHP. Thực ra, Nginx là một "Ninja" đa năng, và kỹ năng mạnh nhất của nó là Reverse Proxy (Proxy ngược).
Giả sử bạn code một app Node.js chạy ở port 3000, một app Go chạy ở port 8080. Bạn không thể bắt khách hàng gõ domain.com:3000 được, quá nghiệp dư và rủi ro bảo mật!
Lúc này, Nginx đứng ra làm Cửa hải quan (Reverse Proxy) ở port 80 (HTTP) và 443 (HTTPS):
server {
listen 80;
server_name myapp.com;
location /api/node/ {
# Chuyển hướng âm thầm vào app Node.js
proxy_pass http://127.0.0.1:3000;
}
location /api/go/ {
# Chuyển hướng âm thầm vào app Golang
proxy_pass http://127.0.0.1:8080;
}
}
Khách hàng chỉ nhìn thấy myapp.com, hoàn toàn mù tịt về các port hay kiến trúc Microservices phức tạp ẩn bên trong server của bạn. Nginx đã che chắn toàn bộ!
3. Mổ xẻ file config "chuẩn cơm mẹ nấu" của Laravel
Anh em làm Laravel chắc chắn đã copy/paste đoạn config Nginx này hàng chục lần, nhưng liệu có bao giờ anh em đọc kỹ từng dòng? Hãy xem Nginx làm phép thuật gì để chạy được Laravel nhé:
server {
listen 80;
server_name laravel-app.com;
root /var/www/laravel/public; # Thư mục gốc chứa mã nguồn
index index.php index.html;
# DÒNG PHÉP THUẬT QUAN TRỌNG NHẤT:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Bắt Nginx gửi file .php cho PHP-FPM xử lý
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
Bí mật của Laravel Routing nằm ở dòng lệnh try_files:
Khi user truy cập [laravel-app.com/users/profile](https://laravel-app.com/users/profile), Nginx sẽ làm theo thứ tự sau:
$uri: Tìm xem có file tĩnh nào tên làprofiletrong thư mục public không? (Không có).$uri/: Tìm xem có thư mục nào tên làprofile/không? (Không có)./index.php?$query_string: Cuối cùng, nó nhồi tất cả phần đuôi URL (/users/profile) chuyển hết cho fileindex.phpxử lý. Từ đây, fileindex.phpbắt đầu chạy, nạp Core của Laravel, đẩy vào Route và sinh ra Request object như bài viết trước mình đã phân tích! Không có dòngtry_filesnày, Laravel Routing báo lỗi 404 ngay lập tức!
4. Những cấu hình "Hạng Nặng" ít ai để ý
Làm Senior, cấu hình Nginx chạy được web là chưa đủ. Bạn phải ép nó tối ưu đến từng nanomet.
Tip 1: Gzip Compression (Ép cân cho Server)
Đừng trả về cho trình duyệt một cục CSS/JS nặng 1MB. Hãy cấu hình Nginx nén nó lại trước khi gửi qua mạng. Băng thông giảm đi cả chục lần, web load nhanh như chớp.
gzip on;
gzip_comp_level 5; # Mức độ nén (1-9)
gzip_types text/plain text/css application/json application/javascript text/xml;
Tip 2: Client Max Body Size (Chống lỗi Upload)
Khách hàng phàn nàn không thể upload video/ảnh lớn báo lỗi 413 Request Entity Too Large? Dù bạn có cấu hình PHP cho upload 100MB, nhưng Nginx mặc định chỉ cho phép request tối đa 1MB. Nginx chặn ngay từ cửa thì PHP có xin cũng vô ích.
# Đặt vào khối server { ... }
client_max_body_size 100M;
Tip 3: Bộ đệm Cache cho Static Files
Với file ảnh .jpg, .png, bạn không cần Nginx phải đọc từ ổ cứng liên tục. Hãy lệnh cho trình duyệt của khách tự lưu Cache!
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d; # Cho trình duyệt lưu cache 1 năm
add_header Cache-Control "public, no-transform";
}
5. Lời kết
Nginx không chỉ là một Web Server, nó là bộ não điều phối lưu lượng, là lớp khiên bảo vệ, và là cỗ máy gia tốc cho mọi ứng dụng Backend. Cho dù bạn code PHP, Python, Ruby hay Go, kỹ năng thao tác với Nginx là điều bắt buộc phải có trên con đường trở thành một Backend/DevOps Engineer thực thụ.
Lần tới, khi setup một dự án mới, đừng chỉ copy-paste config mù quáng nữa nhé! Hãy thử viết lại từng dòng và hiểu rõ "Lễ tân" của mình đang làm gì.
Anh em có cấu hình Nginx nào độc đáo dùng để Rate Limit hay Load Balancer không? Comment chém gió cùng mọi người nhé!
Chúc anh em server luôn xanh!
All Rights Reserved