php-fpm:解決 memory leak 問題、 pm.max_children 相關設定

2021052516:41
php-fpm 記憶體洩漏 (memory leak) 似乎無解
可能是 php-fpm 本身的問題或是某個 extension 造成
不過可以改一些參數,讓有問題的 process 早一點投胎(自行消滅),不要一直佔著毛坑

PHP-FPM 主要的設定:
pm = dynamic
pm.max_children = 100  #這個值 要依據記憶體總量來計算,後面說明
pm.start_servers = 5   #default 5  ,官方公式 = min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.min_spare_servers = 5   #default 5  The desired minimum number of idle server processes.
pm.max_spare_servers = 35 #default 35  The desired maximum number of idle server processes.
pm.max_requests = 300  #default 0
   這個很重要,不要設為 0
   是指單一個 php-fpm process 最多累積處理多少次 requests
   當一個 process 處理的連線數 (requests) 到達此數字時,
   就強制關閉此 process ,並在產生另一個新的 process
   主要是避免 memory leak 的問題
   看 php-fpm 記憶體耗用情形,若耗用嚴重 就設定小一點 如 100~300,讓他早死早超生
    (低、中、高流量網站 得視情況挑整此數字)
   (ps: 在 apache httpd 中 也有個類似的參數 MaxConnectionsPerChild )
pm.process_idle_timeout = 10s  #default 10s The number of seconds after which an idle process will be killed.

以上 pm.max_children 、pm.max_requests 兩個特別需要注意


計算  pm.max_children 方式
先查詢目前 php-fpm 用多少記憶體
這是 php-fpm 剛執行時的資料
$ ps -ylC php-fpm --sort:rss
S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
S    48 20058 30058  0  80   0  6444 347320 -     ?        00:00:00 php-fpm
S    48 20059 30058  0  80   0  6444 347320 -     ?        00:00:00 php-fpm
S    48 20061 30058  0  80   0  6444 347320 -     ?        00:00:00 php-fpm
S     0 30058     1  0  80   0 24040 347320 -     ?        00:00:54 php-fpm
S    48 20057 30058 24  80   0 58560 350132 -     ?        00:00:00 php-fpm
S    48 20060 30058  6  80   0 59096 350132 -     ?        00:00:00 php-fpm

這是 php-fpm 執行 1小時的資料
RSS 平均變成 300MB
$ ps -ylC php-fpm --sort:rss
S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
S     0 30058     1  0  80   0 24120 347320 -     ?        00:00:54 php-fpm
S    48 20080 30058  2  80   0 123888 351156 -    ?        00:00:01 php-fpm
S    48 20061 30058  3  80   0 128344 351193 -    ?        00:00:01 php-fpm
S    48 20058 30058  3  80   0 133544 352271 -    ?        00:00:01 php-fpm
S    48 20060 30058  1  80   0 138920 352217 -    ?        00:00:00 php-fpm
S    48 20081 30058  2  80   0 139492 351180 -    ?        00:00:01 php-fpm
S    48 20057 30058  5  80   0 157816 370652 -    ?        00:00:02 php-fpm
S    48 20059 30058  2  80   0 166856 370883 -    ?        00:00:01 php-fpm
S    48 20076 30058  3  80   0 177448 352553 -    ?        00:00:01 php-fpm
RSS (resident set size) 行即是 php-fpm 用的記憶體(不含swap),單位 kB
*RSS 即記憶體佔用量(kb) 因process之間會共享記憶體 所以此數字會偏大


RSS 相當於 top 畫面中的 SHR,參考資料
算出 php-fpm "平均"使用多少記憶體:
$ ps --no-headers -o "rss" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"M") }'
172M

算出 php-fpm "總"使用多少記憶體:
$ ps --no-headers -o "rss" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/1024,"M") }' 
8744M

**RSS算出來 並非真正占用的記憶體數量

機器有 16GB 記憶體,扣除作業系統以及其它程式需要的記憶體數量
大約剩 13GB
(13G*1024) / 172M = 77 = pm.max_children
(若懶的計算,這裡有表格幫你自動算出 pm.max_children )


參考:
Adjusting child processes for PHP-FPM (Nginx)

An Introduction to PHP-FPM Tuning



另外,每個 php-fpm 每個 process 的 requests 數目,可以在監控介面中查詢
參考
Nginx 啟用 PHP-FPM 服務狀態監控網頁教學




補充

這是比對 php-fpm status 畫面 (http://xxx/status ) 中的資料
幾個 process 處理不到 300 requests 就耗用 500+MB 的記憶體
S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
S    48  2470 21320  0  80   0 549284 340628 -    ?        00:00:33 php-fpm    requests: 297
S    48  4287 21320  0  80   0 542868 341143 -    ?        00:00:25 php-fpm    requests: 234
S    48  6691 21320  0  80   0 518856 341706 -    ?        00:00:19 php-fpm    requests: 169
S    48 15925 21320  0  80   0 214956 319260 -    ?        00:00:01 php-fpm    requests: 18
S    48 15938 21320  0  80   0 214564 338446 -    ?        00:00:01 php-fpm
S    48 16270 21320  0  80   0 238236 340770 -    ?        00:00:01 php-fpm
S    48 16283 21320  0  80   0 201124 340169 -    ?        00:00:00 php-fpm
S    48 16287 21320  0  80   0 234524 339745 -    ?        00:00:01 php-fpm
S    48 16299 21320  0  80   0 215240 340266 -    ?        00:00:00 php-fpm
S    48 16437 21320  0  80   0 221740 340098 -    ?        00:00:01 php-fpm
S     0 21320     1  0  80   0 28732 316150 -     ?        00:00:04 php-fpm




php-fpm status 設定方式

apache httpd 設定:
<LocationMatch "/php_fpm_status">
    Order Allow,Deny
    Allow from 127.0.0.1 xxx.xxx.xxx.xxx
    ProxyPass unix:/var/run/php-fpm/www.sock|fcgi://localhost/php_fpm_status
</LocationMatch>

php-fpm 設定
pm.status_path = /php_fpm_status

重啟 httpd / php-fpm 即可瀏覽
http:/xxx.xxx.xxx.xxx/php_fpm_status?full
畫面類似:



https://github.com/php/php-src/issues/9687
這裡一段 php 程式 用來測試記憶體洩漏 (memory leak) 的問題
<?php
for ($i = 0; $i < 10; $i++) {
    $test = new Test();
    $test->test_function();
    echo memory_get_usage()/1024/1024;
    echo " MB\n";
}

class Test {
    
    public $data = '';
    
    public function __construct() {
    }
    
    public function test_function() {
        // Generate 4 MB data inside the object
        $this->data = str_repeat('a', 4*1024*1024);
        self::test_function2($this);
    }
    
    public static function test_function2($object) {
        //echo 'Hello'; // Enable this to prevent memory leak
        return;
    }
}

/*
php 7.4.33 cli
4.3774185180664 MB
4.3774490356445 MB
4.3774490356445 MB
4.3774490356445 MB
4.3774490356445 MB
4.3774490356445 MB
4.3774490356445 MB
4.3774490356445 MB
4.3774490356445 MB
4.3774490356445 MB

php 7.4.33 cli   (**跟上一個測試 屬不同的主機)
4.3415145874023 MB
8.3455276489258 MB
12.349510192871 MB
16.353492736816 MB
20.357475280762 MB
24.361457824707 MB
28.365440368652 MB
32.369422912598 MB
36.373405456543 MB
40.377388000488 MB

php-fpm v7.4.33 
4.3382415771484 MB
8.3422241210938 MB
12.346206665039 MB
16.350189208984 MB
20.35417175293 MB
24.358154296875 MB
28.36213684082 MB
32.366119384766 MB
36.370101928711 MB
40.374084472656 MB

php-fpm v8.1.27
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB
4.340446472168 MB

php-fpm v8.2.15
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB
4.3403778076172 MB

*/