Tối ưu hóa PHP-FPM để có hiệu suất cao

Tác giả Network Engineer, T.Hai 01, 2021, 03:40:08 CHIỀU

« Chủ đề trước - Chủ đề tiếp »

0 Thành viên và 1 Khách đang xem chủ đề.

Tối ưu hóa PHP-FPM để có hiệu suất cao


1. Giới thiệu.

PHP có ở khắp mọi nơi và được cho là ngôn ngữ được triển khai rộng rãi nhất trên Internet Web.

Tuy nhiên, nó không chính xác được biết đến với khả năng hiệu suất cao, đặc biệt là khi nói đến các hệ thống đồng thời cao. Và đó là lý do mà đối với các trường hợp sử dụng chuyên biệt như vậy, các ngôn ngữ như Node (vâng, tôi biết nó không phải là ngôn ngữ), Go và Elixir đang chiếm ưu thế.

Điều đó nói rằng, có rất nhiều bạn có thể làm để cải thiện hiệu suất PHP trên máy chủ của mình. Bài viết này tập trung vào php-FPM, khía cạnh của mọi thứ, đó là cách tự nhiên để cấu hình trên máy chủ của bạn nếu bạn đang sử dụng Nginx.

Trong trường hợp bạn biết php-FPM là gì, hãy chuyển sang phần tối ưu hóa.

2. PHP-FPM là gì?

Không có nhiều nhà phát triển quan tâm đến khía cạnh DevOps của mọi thứ, và ngay cả trong số những người làm như vậy, rất ít người biết điều gì đang diễn ra. Điều thú vị là khi trình duyệt gửi một yêu cầu đến một máy chủ chạy PHP, thì PHP không phải là điểm tạo nên điểm tiếp xúc đầu tiên, thay vào đó, đó là máy chủ HTTP, các máy chủ chính là Apache và Nginx. Sau đó, những máy chủ web này phải tiếnết định cách kết nối với PHP và chuyển loại yêu cầu, dữ liệu và tiêu đề cho nó.


Trong các ứng dụng PHP hiện đại, phần tìm tập tin ở trên là phần index.php mà máy chủ được cấu hình để ủy tiếnền tất cả các yêu cầu.

Bây giờ, chính xác máy chủ web kết nối với PHP đã phát triển như thế nào, và bài viết này sẽ rất dài nếu chúng ta đi sâu vào tất cả các vấn đề. Nhưng nói một cách đại khái, trong thời gian Apache thống trị như một máy chủ web được lựa chọn, PHP là một mô-đun được cài đặt bao gồm bên trong máy chủ.

Vì vậy, bất cứ khi nào nhận được yêu cầu, máy chủ sẽ bắt đầu một tiến trình mới, tiến trình này sẽ tự động bao gồm PHP và thực thi nó. Phương thức này được gọi là mod_php viết tắt của "PHP as a module". Cách tiếp cận này có những hạn chế của nó mà Nginx đã vượt qua php-FPM.

Trong php-FPM trách nhiệm quản lý PHP, các tiến trình nằm trong chương trình PHP trong máy chủ. Nói cách khác, máy chủ web (trong trường hợp của chúng tôi là Nginx) không quan tâm đến việc PHP đang ở đâu và nó được tải như thế nào, miễn là nó biết cách gửi và nhận dữ liệu từ nó. Nếu muốn, bạn có thể coi PHP trong trường hợp này là một máy chủ khác, tự nó quản lý một số tiến trình PHP con cho các yêu cầu đến (vì vậy, chúng ta có yêu cầu đến một máy chủ, được máy chủ nhận và được chuyển đến máy chủ).

Nếu bạn đã thực hiện bất kỳ thiết lập Nginx nào hoặc thậm chí chỉ cần bắt đầu vào chúng, bạn sẽ gặp một thứ như sau:

Mã nguồn [Chọn]
  location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.2-FPM.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

Dòng mà chúng ta quan tâm là fastcgi_pass unix:/run/php/php7.2-FPM.sock:, cho biết Nginx giao tiếp với tiến trình PHP thông qua socket có tên php7.2-FPM.sock. Vì vậy, đối với mỗi yêu cầu đến, Nginx ghi dữ liệu thông qua tập tin này và khi nhận được kết quả, gửi lại cho trình duyệt.

Một lần nữa, mình phải nhấn mạnh rằng đây không phải là bức tranh đầy đủ nhất hoặc chính xác nhất về những gì đang diễn ra, nhưng nó hoàn toàn chính xác cho hầu hết các nhiệm vụ DevOps.

Bỏ qua điều đó, hãy tóm tắt lại những gì chúng ta đã học được cho đến nay:

  • PHP không trực tiếp nhận các yêu cầu do trình duyệt gửi. Các máy chủ web như Nginx đầu tiên đánh chặn chúng.
  • Máy chủ web biết cách kết nối với tiến trình PHP và chuyển tất cả dữ liệu yêu cầu (theo nghĩa đen là dán mọi thứ vào) sang PHP.
  • Khi PHP hoàn thành phần việc của mình, nó sẽ gửi phản hồi trở lại máy chủ web, máy chủ này sẽ gửi phản hồi trở lại máy khách (hoặc trình duyệt, trong hầu hết các trường hợp).



Nhưng bây giờ xuất hiện câu hỏi hàng triệu đô la: PHP-FPM chính xác là gì?

Phần "FPM" trong PHP là viết tắt của "Fast Process Manager", đây chỉ là một cách nói hoa mỹ để nói rằng PHP chạy trên một máy chủ không phải là một tiến trình đơn lẻ, mà là một số tiến trình PHP được tạo ra, trình điều khiển và bị huỷ do quản lý tiến trình FPM này tắt. Đó là trình quản lý tiến trình này mà máy chủ web chuyển các yêu cầu đến.

PHP-FPM tự bản thân nó là một lỗ hổng, vì vậy hãy thoải mái khám phá nếu bạn muốn, nhưng đối với mục đích của chúng tôi, phần giải thích này sẽ làm được.

3. Tại sao phải tối ưu hóa PHP-FPM?

Vậy tại sao phải lo lắng về tất cả các điệu nhảy này khi mọi thứ đang hoạt động tốt? Tại sao không để mọi thứ như chúng vốn có.

Trớ trêu thay, đó chính xác là lời khuyên mà tôi đưa ra cho hầu hết các trường hợp sử dụng. Nếu thiết lập của bạn đang hoạt động tốt và không có các trường hợp sử dụng bất thường, hãy sử dụng các giá trị mặc định. Tuy nhiên, nếu bạn đang muốn mở rộng quy mô vượt ra ngoài một máy, thì việc vắt kiệt tối đa từ một máy là điều cần thiết vì nó có thể cắt giảm một nửa hóa đơn máy chủ (hoặc thậm chí nhiều hơn).

Một điều khác cần nhận ra là Nginx được xây dựng để xử lý khối lượng công việc khổng lồ. Nó có khả năng xử lý hàng nghìn kết nối cùng một lúc, nhưng nếu điều đó không đúng với thiết lập PHP của bạn, bạn sẽ lãng phí tài nguyên vì Nginx sẽ phải đợi PHP kết thúc với tiến trình hiện tại và chấp nhận tiếp theo, phủ định một cách rõ ràng bất kỳ lợi thế nào mà Nginx được xây dựng để cung cấp.

Vì vậy, với điều đó, hãy xem chính xác những gì chúng ta sẽ thay đổi khi cố gắng tối ưu hóa php-FPM.

4. Làm thế nào để tối ưu hóa PHP-FPM?

Vị trí tập tin cấu hình cho php-FPM có thể khác nhau trên máy chủ, vì vậy bạn sẽ cần thực hiện một số nghiên cứu để định vị tập tin. Bạn có thể sử dụng lệnh find nếu trên UNIX. Trên Ubuntu của mình, đường dẫn là /etc/php/7.2/FPM/php-FPM.conf. Tất nhiên, 7.2 là phiên bản PHP mà tôi đang chạy.

Đây là vài dòng đầu tiên của tập tin này trông như thế nào:

Mã nguồn [Chọn]
;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;

; All relative paths in this configuration file are relative to PHP's install
; prefix (/usr). This prefix can be dynamically changed by using the
; '-p' argument from the command line.

;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;

[global]
; Pid file
; Note: the default prefix is /var
; Default Value: none
pid = /run/php/php7.2-FPM.pid

; Error log file
; If it's set to "syslog", log is sent to syslogd instead of being written
; into a local file.
; Note: the default prefix is /var
; Default Value: log/php-FPM.log
error_log = /var/log/php7.2-FPM.log

Một vài điều sẽ rõ ràng ngay lập tức: dòng pid = /run/php/php7.2-FPM.pid cho chúng ta biết tập tin nào chứa id tiến trình của tiến trình php-FPM.

Chúng ta cũng thấy đó /var/log/php7.2-FPM.log là nơi php-FPM sẽ lưu trữ các bản ghi của nó.

Bên trong tập tin này, thêm ba biến khác như sau:

Mã nguồn [Chọn]
emergency_restart_threshold 10
emergency_restart_interval 1m
process_control_timeout 10s

Hai cài đặt đầu tiên là cảnh báo và cho tiến trình php-FPM biết rằng nếu mười tiến trình con không thành công trong vòng một phút, thì tiến trình php-FPM chính sẽ tự khởi động lại.

Điều này nghe có vẻ không mạnh mẽ, nhưng PHP là một quá trình tồn tại trong thời gian ngắn làm rò rỉ bộ nhớ, vì vậy việc khởi động lại quá trình chính trong trường hợp lỗi cao có thể giải quyết được rất nhiều vấn đề.

Tùy chọn thứ ba process_control_timeout, yêu cầu các tiến trình con đợi khoảng thời gian này trước khi thực hiện tín hiệu nhận được từ tiến trình mẹ. Điều này hữu ích trong trường hợp các tiến trình con đang ở giữa một cái gì đó khi các tiến trình mẹ gửi một tín hiệu KILL chẳng hạn. Với mười giây trong tay, chúng sẽ có cơ hội hoàn thành nhiệm vụ của mình và thoát ra một cách duyên dáng hơn.

Đáng ngạc nhiên, đây không phải là phần tiến trình php-FPM. Đó là bởi vì để phục vụ các yêu cầu web, tiến trình php-FPM tạo ra một nhóm tiến trình mới, sẽ có một cấu hình riêng biệt. Trong trường hợp của mình, hóa ra là tên nhóm www và tập tin tôi muốn chỉnh sửa  /etc/php/7.2/FPM/pool.d/www.conf.

Hãy xem tập tin này bắt đầu như thế nào:

Mã nguồn [Chọn]
; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[www]

; Per pool prefix
; It only applies on the following directives:
; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
; - 'chdir'
; - 'php_values'
; - 'php_admin_values'
; When not set, the global prefix (or /usr) applies instead.
; Note: This directive can also be relative to the global prefix.
; Default Value: none
;prefix = /path/to/pools/$pool

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
user = www-data
group = www-data

Xem nhanh ở cuối đoạn mã trên sẽ giải đáp được thắc mắc tại sao tiến trình máy chủ chạy như thế www-data. Nếu bạn gặp phải vấn đề về quyền đối với tập tin khi thiết lập trang web của mình, bạn có thể đã thay đổi chủ sở hữu hoặc nhóm của thư mục thành www-data, do đó cho phép tiến trình PHP có thể ghi vào tập tin nhật ký và tải lên tài liệu, v.v.

Cuối cùng, chúng ta đến nguồn gốc của vấn đề, cài đặt trình quản lý tiến trình (pm). Nói chung, bạn sẽ thấy các giá trị mặc định như sau:

Mã nguồn [Chọn]
pm = dynamic
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200

Vậy, "động" ở đây có nghĩa là gì? Mình nghĩ rằng các tài liệu chính thức giải thích tốt nhất điều này (ý mình là, đây phải là một phần của tập tin bạn đang chỉnh sửa, nhưng mình đã sao chép nó ở đây đề phòng trường hợp sai sót):

Mã nguồn [Chọn]
; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives. With this process management, there will be
;             always at least 1 children.
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is greater than this
;                                    number then some children will be killed.
;  ondemand - no children are created at startup. Children will be forked when
;             new requests will connect. The following parameter are used:
;             pm.max_children           - the maximum number of children that
;                                         can be alive at the same time.
;             pm.process_idle_timeout   - The number of seconds after which
;                                         an idle process will be killed.
; Note: This value is mandatory.

Vì vậy, chúng ta thấy rằng có ba giá trị có thể có:

  • Tĩnh: Một số tiến trình PHP cố định sẽ được duy trì bất kể điều gì.
  • Động: Chúng tôi có thể chỉ định số lượng tiến trình tối thiểu và tối đa php-FPM sẽ tiếp tục tồn tại tại bất kỳ thời điểm nhất định nào.
  • ondemand: Các tiến trình được tạo và hủy theo yêu cầu.

Vì vậy, làm thế nào để các cài đặt này quan trọng?

Nói một cách dễ hiểu, nếu bạn có một trang web có lưu lượng truy cập thấp, thì cài đặt "động" hầu như là lãng phí tài nguyên. Giả sử rằng bạn đã pm.min_spare_servers đặt thành 3, ba tiến trình PHP sẽ được tạo và duy trì ngay cả khi không có lưu lượng truy cập trên trang web. Trong những trường hợp như vậy, "ondemand" là một lựa chọn tốt hơn, cho phép hệ thống quyết định khi nào khởi chạy các tiến trình mới.

Mặt khác, các trang web xử lý lượng lớn lưu lượng truy cập hoặc phải phản hồi nhanh sẽ bị trừng phạt trong cài đặt này. Việc tạo một tiến trình PHP mới, biến nó thành một phần của một nhóm và giám sát nó, là cách tốt nhất nên tránh.

Việc sử dụng sẽ pm = static sửa số lượng tiến trình con, cho phép sử dụng tối đa tài nguyên hệ thống để phục vụ các yêu cầu thay vì quản lý PHP. Nếu bạn đi theo con đường này, hãy cẩn thận rằng nó có những nguyên tắc và cạm bẫy. Một bài báo khá dày đặc nhưng rất hữu ích về nó là ở đây.

5. Tóm lại.

Vì các bài viết về hiệu suất web có thể gây ra chiến tranh hoặc khiến mọi người nhầm lẫn, mình cảm thấy rằng cần có một vài từ trước khi chúng ta kết thúc bài viết này. Điều chỉnh hiệu suất cũng giống như phỏng đoán và nghệ thuật đen tối vì nó là kiến thức hệ thống.

Ngay cả khi bạn biết tất cả các cài đặt php-FPM thuộc lòng, thành công vẫn không được đảm bảo. Nếu bạn không có manh mối nào về sự tồn tại của nó php-FPM, thì bạn không cần phải mất thời gian lo lắng về nó. Chỉ cần tiếp tục làm những gì bạn đang làm và tiếp tục.

Đồng thời, tránh kết thúc là một kẻ nghiện biểu diễn. Bạn có thể có được hiệu suất tốt hơn nữa bằng cách biên dịch lại PHP từ đầu và xóa tất cả các mô-đun mà bạn sẽ không sử dụng, nhưng cách tiếp cận này không đủ lành mạnh trong môi trường sản phẩm. Toàn bộ ý tưởng của việc tối ưu hóa một thứ gì đó là xem xét liệu nhu cầu của bạn có khác với các giá trị mặc định (điều mà họ hiếm khi làm) Và thực hiện các thay đổi nhỏ nếu cần.

Nếu bạn chưa sẵn sàng dành thời gian tối ưu hóa các máy chủ PHP của mình, thì bạn có thể cân nhắc sử dụng một nền tảng đáng tin cậy như Kinsta, người đảm nhận việc tối ưu hóa hiệu suất và bảo mật.