PHP-php-fpm配置優化
前言:
1.少安裝PHP模塊, 費內存
2.調高linux內核打開文件數量,可以使用這些命令(必須是root帳號)(我是修改/etc/rc.local,加入ulimit -SHn 51200的)
echo `ulimit -HSn 65536` >> /etc/profile echo `ulimit -HSn 65536` >> /etc/rc.local source /etc/profile
如果`ulimit -n`數量依舊不多(即上面配置沒生效)的話, 可以在 /etc/security/limits.conf 文件最后加上
* soft nofile 51200 * hard nofile 51200
1.與Nginx使用Unix域Socket通信(Nginx和php-fpm在同一台服務器)
Unix域Socket因為不走網絡,的確可以提高Nginx和php-fpm通信的性能,但在高並發時會不穩定。
Nginx會頻繁報錯:connect() to unix:/dev/shm/php-fcgi.sock failed (11: Resource temporarily unavailable) while connecting to upstream
可以通過下面兩種方式提高穩定性:
1)調高nginx和php-fpm中的backlog
配置方法為:在nginx配置文件中這個域名的server下,在listen 80后面添加default backlog=1024。
同時配置php-fpm.conf中的listen.backlog為1024,默認為128。
2)增加sock文件和php-fpm實例數
再新建一個sock文件,在Nginx中通過upstream模塊將請求負載均衡到兩個sock文件背后的兩套php-fpm實例上。
2.php-fpm參數調優
pm = dynamic; 表示使用哪種進程數量管理方式
dynamic表示php-fpm進程數是動態的,最開始是pm.start_servers指定的數量,如果請求較多,則會自動增加,保證空閑的進程數不小於pm.min_spare_servers,如果進程數較多,也會進行相應清理,保證多余的進程數不多於pm.max_spare_servers
static表示php-fpm進程數是靜態的, 進程數自始至終都是pm.max_children指定的數量,不再增加或減少
pm.max_children = 300; 靜態方式下開啟的php-fpm進程數量
pm.start_servers = 20; 動態方式下的起始php-fpm進程數量
pm.min_spare_servers = 5; 動態方式下的最小php-fpm進程數量
pm.max_spare_servers = 35; 動態方式下的最大php-fpm進程數量
如果pm為static, 那么其實只有pm.max_children這個參數生效。系統會開啟設置數量的php-fpm進程
如果pm為dynamic, 那么pm.max_children參數失效,后面3個參數生效。系統會在php-fpm運行開始的時候啟動pm.start_servers個php-fpm進程,然后根據系統的需求動態在pm.min_spare_servers和pm.max_spare_servers之間調整php-fpm進程數
那么,對於我們的服務器,選擇哪種pm方式比較好呢?事實上,跟Apache一樣,運行的PHP程序在執行完成后,或多或少會有內存泄露的問題。這也是為什么開始的時候一個php-fpm進程只占用3M左右內存,運行一段時間后就會上升到20-30M的原因了。
對於內存大的服務器(比如8G以上)來說,指定靜態的max_children實際上更為妥當,因為這樣不需要進行額外的進程數目控制,會提高效率。因為頻繁開關php-fpm進程也會有時滯,所以內存夠大的情況下開靜態效果會更好。數量也可以根據 內存/30M 得到,比如8GB內存可以設置為100,那么php-fpm耗費的內存就能控制在 2G-3G的樣子。如果內存稍微小點,比如1G,那么指定靜態的進程數量更加有利於服務器的穩定。這樣可以保證php-fpm只獲取夠用的內存,將不多的內存分配給其他應用去使用,會使系統的運行更加暢通。
對於小內存的服務器來說,比如256M內存的VPS,即使按照一個20M的內存量來算,10個php-cgi進程就將耗掉200M內存,那系統的崩潰就應該很正常了。因此應該盡量地控制php-fpm進程的數量,大體明確其他應用占用的內存后,給它指定一個靜態的小數量,會讓系統更加平穩一些。或者使用動態方式,因為動態方式會結束掉多余的進程,可以回收釋放一些內存,所以推薦在內存較少的服務器或VPS上使用。具體最大數量根據 內存/20M 得到。比如說512M的VPS,建議pm.max_spare_servers設置為20。至於pm.min_spare_servers,則建議根據服務器的負載情況來設置,比較合適的值在5~10之間。
在4G內存的服務器上200就可以(我的1G測試機,開64個是最好的,建議使用壓力測試獲取最佳值)
pm.max_requests = 10240;
nginx php-fpm配置過程中最大問題是內泄漏出問題:服務器的負載不大,但是內存占用迅速增加,很快吃掉內存接着開始吃交換分區,系統很快掛掉!其實根據官方的介紹,php-cgi不存在內存泄漏,每個請求完成后php-cgi會回收內存,但是不會釋放給操作系統,這樣就會導致大量內存被php-cgi占用。
官方的解決辦法是降低PHP_FCGI_MAX_REQUESTS的值,如果用的是php-fpm,對應的php-fpm.conf中的就是max_requests,該值的意思是發送多少個請求后會重啟該線程,我們需要適當降低這個值,用以讓php-fpm自動的釋放內存,不是大部分網上說的51200等等,實際上還有另一個跟它有關聯的值max_children,這個是每次php-fpm會建立多少個進程,這樣實際上的內存消耗是max_children*max_requests*每個請求使用內存,根據這個我們可以預估一下內存的使用情況,就不用再寫腳本去kill了。
request_terminate_timeout = 30;
最大執行時間, 在php.ini中也可以進行配置(max_execution_time)
request_slowlog_timeout = 2; 開啟慢日志
slowlog = log/$pool.log.slow; 慢日志路徑
rlimit_files = 1024; 增加php-fpm打開文件描述符的限制
3.php-fpm的高CPU使用率排查方法
1)使用top命令, 直接執行top命令后,輸入1就可以看到各個核心的CPU使用率。而且通過top -d 0.1可以縮短采樣時間
2)查詢php-fpm慢日志
grep -v "^$" www.log.slow.tmp | cut -d " " -f 3,2 | sort | uniq -c | sort -k1,1nr | head -n 50
5181 run() /www/test.net/framework/web/filters/CFilter.php:41 5156 filter() /www/test.net/framework/web/filters/CFilterChain.php:131 2670 = /www/test.net/index.php 2636 run() /www/test.net/application/controllers/survey/index.php:665 2630 action() /www/test.net/application/controllers/survey/index.php:18 2625 run() /www/test.net/framework/web/actions/CAction.php:75 2605 runWithParams() /www/test.net/framework/web/CController.php:309 2604 runAction() /www/test.net/framework/web/filters/CFilterChain.php:134 2538 run() /www/test.net/framework/web/CController.php:292 2484 runActionWithFilters() /www/test.net/framework/web/CController.php:266 2251 run() /www/test.net/framework/web/CWebApplication.php:276 1799 translate() /www/test.net/application/libraries/Limesurvey_lang.php:118 1786 load_tables() /www/test.net/application/third_party/php-gettext/gettext.php:254 1447 runController() /www/test.net/framework/web/CWebApplication.php:135
參數解釋:
sort: 對單詞進行排序
uniq -c: 顯示唯一的行,並在每行行首加上本行在文件中出現的次數
sort -k1,1nr: 按照第一個字段,數值排序,且為逆序
head -10: 取前10行數據
3)用strace跟蹤進程
a)利用nohup將strace轉為后台執行,直到attach上的php-fpm進程死掉為止:
nohup strace -T -p 13167 > 13167-strace.log &
參數說明:
-c 統計每一系統調用的所執行的時間,次數和出錯的次數等.
-d 輸出strace關於標准錯誤的調試信息.
-f 跟蹤由fork調用所產生的子進程.
-o filename,則所有進程的跟蹤結果輸出到相應的filename
-F 嘗試跟蹤vfork調用.在-f時,vfork不被跟蹤.
-h 輸出簡要的幫助信息.
-i 輸出系統調用的入口指針.
-q 禁止輸出關於脫離的消息.
-r 打印出相對時間關於,,每一個系統調用.
-t 在輸出中的每一行前加上時間信息.
-tt 在輸出中的每一行前加上時間信息,微秒級.
-ttt 微秒級輸出,以秒了表示時間.
-T 顯示每一調用所耗的時間.
-v 輸出所有的系統調用.一些調用關於環境變量,狀態,輸入輸出等調用由於使用頻繁,默認不輸出.
-V 輸出strace的版本信息.
-x 以十六進制形式輸出非標准字符串
-xx 所有字符串以十六進制形式輸出.
-a column
設置返回值的輸出位置.默認為40.
-e execve 只記錄 execve 這類系統調用
-p 主進程號
b)用利用-c參數讓strace幫助匯總,非常方便非常強大!
[root@b28-12 log]# strace -cp 9907 Process 9907 attached - interrupt to quit Process 9907 detached % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 56.61 0.016612 5 3121 read 11.11 0.003259 1 2517 715 stat 8.04 0.002358 7 349 brk 6.02 0.001767 1 1315 poll 4.28 0.001255 6 228 recvfrom 2.71 0.000796 1 671 open 2.54 0.000745 0 2453 fcntl 2.37 0.000696 1 1141 write 1.69 0.000497 1 593 13 access 1.37 0.000403 0 1816 lseek 0.89 0.000262 1 451 22 sendto 0.56 0.000163 1 276 208 lstat 0.49 0.000145 0 384 getcwd 0.31 0.000090 0 1222 fstat 0.28 0.000082 0 173 munmap 0.26 0.000077 0 174 mmap 0.24 0.000069 2 41 socket 0.23 0.000068 0 725 close 0.00 0.000000 0 13 rt_sigaction 0.00 0.000000 0 13 rt_sigprocmask 0.00 0.000000 0 1 rt_sigreturn 0.00 0.000000 0 78 setitimer 0.00 0.000000 0 26 26 connect 0.00 0.000000 0 15 2 accept 0.00 0.000000 0 39 recvmsg 0.00 0.000000 0 26 shutdown 0.00 0.000000 0 13 bind 0.00 0.000000 0 13 getsockname 0.00 0.000000 0 65 setsockopt 0.00 0.000000 0 13 getsockopt 0.00 0.000000 0 8 getdents 0.00 0.000000 0 26 chdir 0.00 0.000000 0 1 futex ------ ----------- ----------- --------- --------- ---------------- 100.00 0.029344 18000 986 total
4.使用Opcode緩存(http://www.cnblogs.com/JohnABC/p/4531038.html)
5.對PHP性能進行監控
xdebug.auto_trace = on
xdebug.auto_profile = on
xdebug.collect_params = on
xdebug.collect_return = on
xdebug.profiler_enable = on
xdebug.trace_output_dir = "/tmp"
xdebug.profiler_output_dir ="/tmp"
6.監測php-fpm線程狀態
nginx配置
location ~ ^/status$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $fastcgi_script_name; }
php-fpm配置
pm.status_path = /status
這樣的話通過http://域名/status就可以看到當前的php情況
下面介紹每個參數的作用:
pool:php-fpm池的名稱,一般都是應該是www
process manage:進程的管理方法,php-fpm支持三種管理方法,分別是static,dynamic和ondemand,一般情況下都是dynamic
start time:php-fpm啟動時候的時間,不管是restart或者reload都會更新這里的時間
start since:php-fpm自啟動起來經過的時間,默認為秒
accepted conn:當前接收的連接數
listen queue:在隊列中等待連接的請求個數,如果這個數字為非0,那么最好增加進程的fpm個數
max listen queue:從fpm啟動以來,在隊列中等待連接請求的最大值
listen queue len:等待連接的套接字隊列大小
idle processes:空閑的進程個數
active processes:活動的進程個數
total processes:總共的進程個數
max active processes:從fpm啟動以來,活動進程的最大個數,如果這個值小於當前的max_children,可以調小此值
max children reached:當pm嘗試啟動更多的進程,卻因為max_children的限制,沒有啟動更多進程的次數。如果這個值非0,那么可以適當增加fpm的進程數
slow requests:慢請求的次數,一般如果這個值未非0,那么可能會有慢的php進程,一般一個不好的mysql查詢是最大的禍首。
7.開啟php-fpm慢日志
slowlog = /usr/local/php/log/php-fpm.log.slow
request_slowlog_timeout = 5s
8.設置php-fpm單次請求最大執行時間,今天碰到一個問題,測試服務器php-fpm一直是被占滿狀態,后來發現是set_time_limit(0),file_get_content(),原因如下:
比如file_get_contents(url)等函數,如果網站反應慢,會一直等在那兒不超時,php-fpm一直被占用。有一個參數 max_execution_time 可以設置 PHP 腳本的最大執行時間,但是,在 php-cgi(php-fpm) 中,該參數不會起效。真正能夠控制 PHP 腳本最大執行時間的是 php-fpm.conf 配置文件中的以下參數。
request_terminate_timeout = 10s
默認值為 0 秒,也就是說,PHP 腳本會一直執行下去。這樣,當所有的 php-cgi 進程都卡在 file_get_contents() 函數時,這台 Nginx+PHP 的 WebServer 已經無法再處理新的 PHP 請求了,Nginx 將給用戶返回“502 Bad Gateway”。可以使用 request_terminate_timeout = 30s,但是如果發生 file_get_contents() 獲取網頁內容較慢的情況,這就意味着 150 個 php-cgi 進程,每秒鍾只能處理 5 個請求,WebServer 同樣很難避免“502 Bad Gateway”。php-cgi進程數不夠用、php執行時間長、或者是php-cgi進程死掉,都會出現502錯誤。
要做到徹底解決,只能讓 PHP 程序員們改掉直接使用 file_get_contents("http://example.com/") 的習慣,而是稍微修改一下,加個超時時間,用以下方式來實現 HTTP GET 請求。要是覺得麻煩,可以自行將以下代碼封裝成一個函數。
<?php $ctx = stream_context_create(array( 'http' => array( 'timeout' => 1 //設置一個超時時間,單位為秒 ) ) ); file_get_contents("http://example.com/", 0, $ctx);
當然,導致 php-cgi 進程 CPU 100% 的原因不只有這一種,那么,怎么確定是 file_get_contents() 函數導致的呢?
首先,使用 top 命令查看 CPU 使用率較高的 php-cgi 進程。
top - 10:34:18 up 724 days, 21:01, 3 users, load average: 17.86, 11.16, 7.69 Tasks: 561 total, 15 running, 546 sleeping, 0 stopped, 0 zombie Cpu(s): 5.9%us, 4.2%sy, 0.0%ni, 89.4%id, 0.2%wa, 0.0%hi, 0.2%si, 0.0%st Mem: 8100996k total, 4320108k used, 3780888k free, 772572k buffers Swap: 8193108k total, 50776k used, 8142332k free, 412088k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 10747 www 18 0 360m 22m 12m R 100.6 0.3 0:02.60 php-cgi 10709 www 16 0 359m 28m 17m R 96.8 0.4 0:11.34 php-cgi 10745 www 18 0 360m 24m 14m R 94.8 0.3 0:39.51 php-cgi 10707 www 18 0 360m 25m 14m S 77.4 0.3 0:33.48 php-cgi 10782 www 20 0 360m 26m 15m R 75.5 0.3 0:10.93 php-cgi 10708 www 25 0 360m 22m 12m R 69.7 0.3 0:45.16 php-cgi 10683 www 25 0 362m 28m 15m R 54.2 0.4 0:32.65 php-cgi 10711 www 25 0 360m 25m 15m R 52.2 0.3 0:44.25 php-cgi 10688 www 25 0 359m 25m 15m R 38.7 0.3 0:10.44 php-cgi 10719 www 25 0 360m 26m 16m R 7.7 0.3 0:40.59 php-cgi
找其中一個 CPU 100% 的 php-cgi 進程的 PID,用以下命令跟蹤一下:
strace -p 10747
如果屏幕顯示:
select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout) select(7, [6], [6], [], {15, 0}) = 1 (out [6], left {15, 0}) poll([{fd=6, events=POLLIN}], 1, 0) = 0 (Timeout)
那么,就可以確定是 file_get_contents() 導致的問題了。(參考:http://zyan.cc/tags/request_terminate_timeout/1/)
9.查看php-fpm啟動時間(可以得出執行了多長時間)
ps -A -o pid,lstart,cmd |grep php-fpm