Select Page

PHP-FPM 被 OOM Killer 殺掉?完整排查與修復實戰紀錄(Ubuntu 16GB RAM 環境)

最近我的伺服器突然出現網站 502 錯誤,檢查後發現 php8.2-fpm 服務竟然被系統強制終止。透過一系列排查,最終確認是 Linux OOM Killer(Out Of Memory) 觸發導致。

這篇文章整理完整排查過程與解決方案,提供給遇到相同問題的人參考。


📌 問題現象

使用 systemctl status php8.2-fpm 發現:

Active: failed (Result: oom-kill)

進一步檢查 kernel log:

Out of memory: Killed process 7057 (php-fpm8.2)
anon-rss:283560kB
shmem-rss:119848kB

代表:

  • 系統記憶體耗盡
  • Linux 啟動 OOM Killer
  • 強制殺掉 php-fpm worker

🔍 進一步分析

查看當時 PHP worker 記憶體使用情況:

ps -o pid,rss,etime,cmd -C php-fpm8.2 --sort=-rss | head -n 20

當時結果顯示:

RSS 約 250MB ~ 325MB/每個 worker

而之前狀態顯示:

Processes active: 80
idle: 0

🔥 問題核心

如果每個 worker 約 300MB:

300MB × 80 = 24GB

但機器只有:

15GB RAM
0GB Swap

👉 記憶體一定會爆。


🧠 伺服器環境

Memory: 15GiB
Swap: 0

這代表:

  • 沒有 swap 作為緩衝
  • 一旦瞬間記憶體尖峰,直接 OOM

🛠 解決方案


✅ 1️⃣ 建立 8GB Swap(防止再次 OOM)

sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

確認:

swapon --show
free -h

結果:

Swap: 8.0GiB

建議調整 swappiness

echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl -p /etc/sysctl.d/99-swappiness.conf

✅ 2️⃣ 調整 PHP-FPM 設定(關鍵)

編輯:

/etc/php/8.2/fpm/pool.d/www.conf

修改為:

pm = dynamic
pm.max_children = 25
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 10
pm.max_requests = 500

為什麼設 25?

假設每個 worker 約 150MB:

150MB × 25 ≈ 3.7GB

在 15GB RAM 環境下非常安全。


✅ 3️⃣ 開啟 Slow Log(抓出慢請求)

www.conf 加入:

request_slowlog_timeout = 10s
slowlog = /var/log/php8.2-fpm/www-slow.log
request_terminate_timeout = 60s

建立目錄:

sudo mkdir -p /var/log/php8.2-fpm
sudo chown -R www-data:www-data /var/log/php8.2-fpm
sudo systemctl restart php8.2-fpm

查看慢請求:

sudo tail -f /var/log/php8.2-fpm/www-slow.log

📊 調整後效果

重啟後查看:

ps -o pid,rss,etime,cmd -C php-fpm8.2 --sort=-rss | head -n 20

結果:

RSS 約 100MB ~ 170MB
ELAPSED 僅 20 秒左右

✔ 不再出現 300MB 以上肥 worker
✔ 不再出現 active 80
✔ 系統穩定運作


🎯 問題總結

本次 OOM 原因:

  1. pm.max_children 設定過高
  2. 每個 worker 記憶體使用偏高
  3. 沒有 swap 緩衝
  4. 慢請求導致 worker 堆積

最終解法:

  • 限制 max_children
  • 建立 swap
  • 開 slowlog 抓慢請求
  • 設定 max_requests 避免 memory leak

🚀 建議最佳實務

設定建議
Swap一定要有(至少 4GB)
max_children用 RAM / worker RSS 計算
max_requests300~1000
slowlog必開
request_terminate_timeout必設

📌 結論

OOM Killer 不是錯誤,它是保護機制。

真正的問題通常是:

  • PHP-FPM 設定不合理
  • 慢請求堆積
  • 記憶體容量與負載不匹配

只要正確限制 worker 數量並建立 swap,伺服器穩定度會大幅提升。


如果你也遇到 php-fpm 被 OOM kill 的問題,希望這篇實戰紀錄能幫助你快速排查與解決。

相關資訊

Redis提升WP性能:一步步教您如何優化安裝與配置Redis資料庫

Redis提升WP性能:一步步教您如何優化安裝與配置Redis資料庫

Redis作為一款開源的高性能key-value數據庫,已經在眾多頂尖科技公司和網站獲得廣泛應用。本文將引領您走過安裝和配置Redis資料庫的過程,並提供實用的優化技巧,助力您的系統性能達到新的高峰。

為什麼選擇Redis?

首先,讓我們理解一下為什麼您應該選擇Redis。Redis以其極速性能、靈活的數據結構和高可用性成為獨一無二的選擇。它可以作為緩存,消息佇列,以及在高壓情況下用作可靠的數據存儲。

安裝Redis

Ubuntu安裝Redis

sudo apt install redis-server

Windows 安裝 Redis

直接下載微軟製作的安裝包,下載網址 https://github.com/MicrosoftArchive/redis/releases

驗證Redis是否安裝成功

如果是剛安裝好,並且在同一台機器上可以直接打指令 redis-cli 就可以連線

redis-cli
redis-cli ping
redis-cli info

如果是別台機器的話,記得要開防火牆,並且指定 IP 以及指定 Port 和指定密碼

redis-cli -h 192.168.0.X -p 6379 -a 123456

設定Redis

設定檔案通常位於 /etc/redis/redis.conf ,編輯完記得要重開服務 sudo systemctl restart redis ,不確定conf檔案的位置的話,可以用 redis-cli info 查找,會顯示以下資訊

executable:/usr/bin/redis-server
config_file:/etc/redis/redis.conf

sudo nano /etc/redis/redis.conf
  • bind:若要遠端連入Redis伺服器,就會需要設定 bind 0.0.0.0 ::
  • databases:設定可用的資料庫數量,在索引的時候是從0開始數,預設會使用索引值為0的資料庫。這個項目的預設值是16
  • save:設定在一定的間隔時間內若資料庫有發生一定程度的改變,就將記憶體中當下的資料存成檔案(快照)。save的撰寫格式為save <seconds> <changes>save 60 10000表示在60秒內至少有10000個key被改變則做一次快照。
  • requirepass:設定客戶端與Redis伺服器連線時所需要的密碼。預設沒有設定,表示不啟用密碼驗證功能。
  • maxclients:設定最大連線數。預設沒有設定,當作10000
  • maxmemory:設定最大的記憶體使用量,如果記憶體用量達到限制,就會根據maxmemory-policy項目設定的策略來嘗試移除key,如果無法移除,就會使該次插入或修改的操作回傳錯誤。預設沒有設定,表示不限制。
  • maxmemory-policy:記憶體用量達到限制時採取的策略。預設沒有設定,當作noeviction,不移除key。其它策略如下:
    • volatile-lru:根據LRU演算法移除過期的key。
    • allkeys-lru:根據LRU演算法移除key。(不管有沒有過期)
    • volatile-lfu:根據LFU演算法移除過期的key。
    • allkeys-lfu:根據LFU演算法移除key。(不管有沒有過期)
    • volatile-random:隨機移除過期的key。
    • allkeys-random:隨機移除key。(不管有沒有過期)
    • volatile-ttl:移除已過期的key中,TTL最小的key。
#bind 127.0.0.1 ::1
bind 0.0.0.0 ::

重啟服務

sudo systemctl restart redis

清除Redis的資料

利用 redis-cli 進去 Redis 主機後,輸入

flushall

清除單一資料的指令是

flush db0

在 WordPress 中使用 Redis

Redis Object Cache 外掛網址 https://wordpress.org/plugins/redis-cache/

設定 wp-config.php

// adjust Redis host and port if necessary 
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );

// change the prefix and database for each site to avoid cache data collisions
define( 'WP_REDIS_PREFIX', 'my-moms-site' );
define( 'WP_REDIS_DATABASE', 0 ); // 0-15

// reasonable connection and read+write timeouts
define( 'WP_REDIS_TIMEOUT', 1 );
define( 'WP_REDIS_READ_TIMEOUT', 1 );

/* That's all, stop editing! Happy publishing. */
require_once(ABSPATH . 'wp-settings.php');

組態檔

Configuration constantDefaultDescription
WP_REDIS_HOST127.0.0.1The hostname of the Redis server
WP_REDIS_PORT6379The port of the Redis server
WP_REDIS_PATHThe path to the unix socket of the Redis server
WP_REDIS_SCHEMEtcpThe scheme used to connect: tcp or unix
WP_REDIS_DATABASE0The database used by the cache: 0-15
WP_REDIS_PREFIXThe prefix used for all cache keys to avoid data collisions, replaces WP_CACHE_KEY_SALT. Should be human readable, not a “salt”.
WP_REDIS_PASSWORDThe password of the Redis server. Supports Redis ACLs arrays: ['user', 'password']
WP_REDIS_MAXTTL0The maximum time-to-live of cache keys
WP_REDIS_CLIENTThe client used to communicate with Redis: predisphpredis or relay
WP_REDIS_TIMEOUT1The connection timeout in seconds
WP_REDIS_READ_TIMEOUT1The timeout in seconds when reading/writing
WP_REDIS_IGNORED_GROUPS[]Groups that should not be cached between requests in Redis

常用控制指令

CommandDescription
wp redis statusShows the object cache status and diagnostics
wp redis enableEnables the object cache
wp redis disableDisables the object cache
wp redis update-dropinUpdates the object cache drop-in

效能考量,安裝 Phpredis

請先到 phpredis 這邊搜尋對應你平台的檔案,用 Linux Ubuntu 為例,下載 redis-6.0.2 版本 ,並且解壓縮他

wget https://pecl.php.net/get/redis-6.0.2.tgz
tar -zxvf redis-6.0.2.tgz
cd redis-6.0.2

接下來先編譯原檔案

sudo phpize
sudo ./configure
sudo make && sudo make install

完成編譯檔案後,要把 .so 檔案放到 php 的 modules 中,先把該目錄找出來

php -i | grep extension_dir

系統回應我,extension_dir => /usr/lib/php/20220829 => /usr/lib/php/20220829 ,則將剛編譯好,放在 modules 下的 redis.so 複製到正確的目錄中

sudo cp ./modules/redis.so /usr/lib/php/20220829/

複製完畢後,需要去修改 php.ini 檔案,讓他支援 redis,這邊我示範用 Nginx

sudo nano /etc/php/8.2/fpm/php.ini

將 extension 打開,請找到 Dynamic Extensions ,並且在下面加入

extension=redis

設定完成後,重開 php 讓他生效

sudo systemctl restart php8.2-fpm

檢查方法,利用info.php,並且搜尋是否有redis字眼,如下圖就是成功了

關於Redis的大小事

預設安裝完畢,初始話是支援16個資料庫的,分別由編號 0-15 ,要增加資料庫,要去 Redis 的組態檔裡面修改 redis/redis.conf 中,找到 databases ,並且調整數值後,重新啟動即可

databases 16

Redis 讀取資料

redis-cli set my_key "Value"
redis-cli get my_key

Redis 列出所有的 KEY

redis-cli KEYS *

相關文章

詳細的指令操作介紹