0

Ám ảnh "502 Bad Gateway": Giải phẫu PHP-FPM và nghệ thuật tối ưu Server chịu tải cao

Chào anh em cộng đồng Viblo!

Khi mới học code Laravel, chúng ta thường gõ php artisan serve, thấy web chạy ầm ầm và tự nhủ: "Dễ ợt!".

Nhưng khi đưa ứng dụng lên Production (chạy thực tế), bạn không thể dùng lệnh đó được. Bạn phải cài Nginx (hoặc Apache). Và đột nhiên, một ngày hệ thống có event Flash Sale, lượng truy cập tăng vọt, đập vào mặt khách hàng là một màn hình trắng lóa với dòng chữ vô hồn: 502 Bad Gateway hoặc 504 Gateway Timeout.

Bạn vội vàng SSH vào server, check RAM thì thấy cạn kiệt, CPU thì 100%. Kẻ đứng sau giật dây toàn bộ thảm kịch này (và cũng là vị cứu tinh nếu bạn biết cách thuần hóa nó) chính là PHP-FPM.

Hôm nay, chúng ta sẽ bỏ code sang một bên, cùng hóa thân thành System Admin để mổ xẻ PHP-FPM từ gốc đến ngọn nhé. Lên xe!

1. Cỗ máy thời gian: Tại sao chúng ta cần PHP-FPM?

Để hiểu PHP-FPM, bạn phải biết ngày xưa người ta chạy PHP khổ như thế nào:

  • Thời kỳ đồ đá (CGI - Common Gateway Interface): Cứ mỗi một request từ trình duyệt gửi tới, server lại "khởi động" một tiến trình PHP, đọc file php.ini, load thư viện, chạy code trả về kết quả, rồi... GIẾT tiến trình đó. Giống như việc mở quán phở, mỗi lần có 1 khách vào bạn lại đi nhóm lò, nấu xong bát phở rồi... đập bỏ cái lò. Cực kỳ tốn tài nguyên!
  • Thời kỳ quá độ (mod_php): Apache tích hợp thẳng PHP vào trong nó. Nhanh hơn, nhưng sinh ra vấn đề: Web server (Apache) trở nên quá béo và nặng nề. Kể cả khi khách chỉ request một file ảnh .jpg, Apache cũng phải lôi theo cục engine PHP khổng lồ đi theo.

Và thời kỳ hoàng kim xuất hiện: FastCGI & PHP-FPM

  • FastCGI: Giải quyết bài toán nhóm lò. Nó bật sẵn một bầy tiến trình (Worker) luôn luôn chạy nền. Có request tới là ném cho Worker xử lý, xử lý xong Worker KHÔNG CHẾT mà đứng đợi request tiếp theo.
  • PHP-FPM (FastCGI Process Manager): Là một trình quản lý các tiến trình FastCGI xịn xò nhất dành riêng cho PHP.

2. Mô hình Nginx và PHP-FPM: Lễ tân và Nhà bếp

Trong kiến trúc hiện đại, Nginx không hề biết đọc code PHP. Nó chỉ là một anh Lễ tân đẹp trai, đứng đón khách (nhận HTTP Request), phục vụ file tĩnh (ảnh, css, js) cực nhanh.

Khi khách order một món phức tạp (file .php), Lễ tân Nginx sẽ chuyển cái order đó qua cho Nhà bếp (PHP-FPM) thông qua một cánh cửa gọi là unix socket (vd: /run/php/php8.2-fpm.sock) hoặc cổng TCP (127.0.0.1:9000).

Trong nhà bếp PHP-FPM có 2 thành phần:

  1. Master Process (Bếp trưởng): Chạy bằng quyền root, không trực tiếp nấu ăn. Nhiệm vụ của nó là quản lý, sinh ra hoặc giết bớt các đầu bếp (Workers) tùy theo lượng khách.
  2. Worker Processes (Đầu bếp): Chạy bằng quyền user thường (vd: www-data). Đây mới là nơi thực sự nạp code Laravel, kết nối Database và chạy logic.

Lỗi 502 Bad Gateway sinh ra khi nào? Khi anh Lễ tân Nginx ném order vào bếp, nhưng Bếp trưởng báo: "Chết sạch đầu bếp rồi!" (Process bị crash do lỗi code hoặc OOM).

Lỗi 504 Gateway Timeout sinh ra khi nào? Khi Lễ tân ném order, đầu bếp đang nấu nhưng nấu quá lâu (quá tải), Lễ tân đợi mỏi chân (hết timeout) nên xin lỗi khách rồi đuổi khách về.

3. Nghệ thuật config PHP-FPM: Đừng để mặc định!

Nếu bạn cài PHP-FPM và cứ thế chạy, server của bạn đang mang trong mình một "quả bom nổ chậm". Mở file cấu hình Pool của FPM lên (thường ở /etc/php/8.x/fpm/pool.d/www.conf), chúng ta sẽ bắt đầu "độ xe".

A. Chọn chiến thuật quản lý (Process Manager - pm)

Có 3 chế độ quản lý Worker:

  • pm = ondemand: Khách đến mới gọi đầu bếp từ nhà lên. Tiết kiệm RAM nhất nhưng độ trễ (latency) cao nhất. Chỉ dùng cho môi trường Dev hoặc server cực yếu.
  • pm = dynamic: (Mặc định). Bếp trưởng duy trì một lượng đầu bếp tối thiểu. Khách đông thì gọi thêm, khách vắng thì cho nghỉ bớt. Phù hợp với server chạy nhiều website (Shared Hosting).
  • pm = static (Chân ái cho Production hạng nặng): Bếp trưởng luôn duy trì một số lượng đầu bếp cố định (Ví dụ: 50 người). Bất kể vắng hay đông.
  • Tại sao lại dùng static? Việc Master Process liên tục spawn (sinh ra) và kill (giết) các Worker trong chế độ dynamic tốn rất nhiều CPU. Ở các server E-commerce lớn, người ta thà tốn RAM giữ trước tiến trình (static), để đổi lấy việc Request được đáp ứng ngay lập tức với CPU ổn định.

B. Công thức tính pm.max_children (Số đầu bếp tối đa)

Nếu bạn dùng static, bạn phải tính con số pm.max_children. Nếu set quá nhỏ: Khách phải xếp hàng -> 504. Nếu set quá lớn: Vượt quá số RAM thực tế của Server -> Hết RAM, Server chết đứng -> 502.

Công thức thực chiến:

  1. Check xem 1 tiến trình PHP của bạn ăn bao nhiêu RAM (Dùng lệnh top hoặc htop, thường 1 app Laravel ăn khoảng 40MB - 80MB tùy độ nặng).
  2. Tính toán: max_children = (Tổng RAM Server - RAM cho Hệ điều hành & DB) / RAM của 1 Worker

Ví dụ: Server bạn có 4GB RAM. Hệ điều hành và MySQL ăn mất 1.5GB. Bạn còn 2.5GB (2500MB) cho FPM. App Laravel của bạn ăn 50MB/Worker. => max_children = 2500 / 50 = 50. Bạn set pm.max_children = 50 là server sẽ an toàn, không bao giờ lo OOM (Out Of Memory).

4. Tuyệt chiêu "Xương máu" bảo vệ Server

Tuyệt chiêu 1: Chống rò rỉ bộ nhớ với pm.max_requests Dù framework có tốt đến đâu, việc chạy liên tục hàng triệu request sẽ rò rỉ (leak) những byte RAM cực nhỏ. Tích tiểu thành đại, Worker của bạn từ 50MB sẽ phình lên 500MB sau vài ngày.

Hãy set thông số này:

pm.max_requests = 1000

Ý nghĩa: Khi một Đầu bếp nấu xong 1000 bát phở (1000 requests), Bếp trưởng sẽ tự động "bắn bỏ" đầu bếp đó và tuyển ngay một người mới tinh, sạch sẽ thay thế. RAM được reset!

Tuyệt chiêu 2: Bật "Mắt thần" Slowlog để bắt code dở Chỉnh sửa Nginx hay PHP-FPM không giúp hệ thống nhanh hơn nếu code của bạn chậm (VD: Quên đánh index Database, N+1 query). Hãy để PHP-FPM tự động "chỉ điểm" những đoạn code chạy quá lâu:

slowlog = /var/log/php-fpm/$pool.log.slow
request_slowlog_timeout = 3s

Ý nghĩa: Bất kỳ HTTP request nào mất quá 3 giây để thực thi, PHP-FPM sẽ dump thẳng cái Stack Trace (Dòng code nào, ở file nào gây chậm) vào file log. Bạn chỉ việc mở log ra, túm cổ đoạn code đó và refactor. Cực kỳ bá đạo mà không cần cài các tool APM nặng nề!

5. Lời kết

PHP-FPM là cầu nối sinh tử giữa Web Server và Application của bạn. Hiểu được cơ chế hoạt động và cách tính toán bộ nhớ cho FPM, bạn sẽ không còn sợ những đêm Server bị sập bất đắc kỳ tử hay những thông báo 502/504 đầy ám ảnh nữa.

Lần tới mở config lên, đừng để pm = dynamic với thông số bừa bãi nữa nhé! Tính toán kỹ RAM và chuyển sang static nếu server của bạn là một Server chuyên dụng (Dedicated).

Anh em có câu hỏi nào về việc config Nginx/FPM không? Hoặc từng bị sếp "gõ đầu" vì sập server thế nào, chia sẻ xuống dưới nhé!

Chúc anh em server luôn xanh!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí