今天我們不講文法這些老掉牙的東西,我們隨便找一個擴充,來分析一下 php底層 和 mysql 之間的通訊原理。
首先我們來理解一下 php-fpm 的工作原理,php-fpm 是一個 php-cgi 進程管理器,其實就是一個串連池,它和nginx配合的工作原理如下。
我們先從最簡單的靜態方式入手觀察他的工作原理
vim php-fpm.ini
[www]
pm = static
pm.max_children = 5
pm.max_requests = 2
上面三句話的含義是什麼呢:
1、static 表示靜態以靜態方式產生 php-fpm 進程
2、pm.max_children = 5 表示當 php-fpm 啟動時就啟動 5 個 php-fpm 子進程 等待處理 nginx 發過來的請求
3、pm.max_requests = 2 表示每個 php-fpm 子進程處理 2 個請求就銷毀,當然父進程每次看到有銷毀的自然也就會產生新的子進程
我們來簡單驗證一下這個說法:
首先重啟 php-fpm,讓它複位一下
接下來寫一條簡單的語句輸出當前進程ID
echo "當前 php-fpm 進程ID:".posix_getpid();
不斷重新整理瀏覽器觀察輸出變化
當前 php-fpm 進程ID:24548
當前 php-fpm 進程ID:24549
當前 php-fpm 進程ID:24550
當前 php-fpm 進程ID:24547
當前 php-fpm 進程ID:24551
當前 php-fpm 進程ID:24548
當前 php-fpm 進程ID:24549
當前 php-fpm 進程ID:24550
當前 php-fpm 進程ID:24547
當前 php-fpm 進程ID:24551
當前 php-fpm 進程ID:24563
當前 php-fpm 進程ID:24564
當前 php-fpm 進程ID:24565
當前 php-fpm 進程ID:24566
當前 php-fpm 進程ID:24567
當前 php-fpm 進程ID:24563
當前 php-fpm 進程ID:24564
當前 php-fpm 進程ID:24565
當前 php-fpm 進程ID:24566
當前 php-fpm 進程ID:24567
當前 php-fpm 進程ID:24568
當前 php-fpm 進程ID:24569
當前 php-fpm 進程ID:24570
當前 php-fpm 進程ID:24571
當前 php-fpm 進程ID:24572
當前 php-fpm 進程ID:24568
當前 php-fpm 進程ID:24569
當前 php-fpm 進程ID:24570
當前 php-fpm 進程ID:24571
當前 php-fpm 進程ID:24572
可以看得出,第一批id不是按照順序執行的,進程id為24547的進程是在第四位處理的,然後從下面開始,所有id都是順序執行的而且每次產生的一批id都是遞增,是不是有種mysql自增主鍵的趕腳呢?
這裡需要注意的是,無論是靜態還是下面的動態配置方式,只要沒有設定 max_requests ,那麼進程是不會銷毀的,也就是說當一個進程裡面出現死迴圈或者記憶體溢出等導致進程僵死的情況出現的時候,處理的進程就會少一個了
好吧理解了靜態處理方式,我們其實也很容易知道這個方式的弊端了,當然我們平時伺服器不可能就開5個進程每個進程處理2個請求,我們來做一個簡單的加減乘除,看看一個伺服器應該開多少個 php-fpm 合適
首先我們來看看一個簡單的echo需要多少記憶體:
$size = memory_get_usage();
$unit = array('b','kb','mb','gb','tb','pb');
$memory = @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];
echo "當前 php-cgi 進程所使用記憶體:".$memory;
觀察瀏覽器我們可以得到一下資料:
當前 php-cgi 進程所使用記憶體:227.17 kb
也就是說一個簡單的什麼都不乾的php就已經佔用了200多K的記憶體,當然這也不算多。
不過進程多了cpu切換進程速度就會變慢,所以這個數還是需要通過ab等測試載入器才能測試出具體應該開多少比較合理
我們先從200開始,不斷的增加,架設增加到800的時候,效率和400一樣,那我們就沒必要開800那麼多進程浪費記憶體了。
那麼問題就來了,如果同一時間請求出超過400呢?有人說會排隊等待,真的會排隊等待嗎?答案明顯是 php-fpm 是沒能力排隊了,因為處理請求的php-fpm子進程都用完了,那麼等待也就只能是在 nginx 等待,通常一個 nginx 也不只是轉寄請求給 php-fpm 就完事了,他還要處理靜態檔案呢?如果這些php請求導致nginx的請求數過多一直在等待,那麼訪問靜態檔案自然也會卡了,這時候我們就需要配置成下面的動態處理方式。
[www]
pm.max_children = 10
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 8
;pm.max_requests = 2
上面五句話的含義是什麼呢:
1、dynamic 表示靜態以動態方式產生 php-fpm 進程
2、pm.max_children = 10 同時活動的進程數 10個
3、pm.start_servers = 5 表示當 php-fpm 主進程啟動時就啟動 5 個 php-fpm 子進程
4、pm.min_spare_servers = 2 表示最小備用進程數
5、pm.max_spare_servers = 8 表示最大備用進程數
6、pm.max_requests = 2 上面說過就不說了
當前 php-fpm 進程ID:2270
當前 php-fpm 進程ID:2271
當前 php-fpm 進程ID:2272
當前 php-fpm 進程ID:2273
當前 php-fpm 進程ID:2274
當前 php-fpm 進程ID:2270
當前 php-fpm 進程ID:2271
當前 php-fpm 進程ID:2272
當前 php-fpm 進程ID:2273
當前 php-fpm 進程ID:2274
當前 php-fpm 進程ID:2270
當前 php-fpm 進程ID:2271
當前 php-fpm 進程ID:2272
當前 php-fpm 進程ID:2273
當前 php-fpm 進程ID:2274
為什麼這裡沒有重建新的進程?因為pm.max_requests = 2被注釋掉了,這個上面其實已經提及過一次了
我們也可以從 ps 看出這批進程id
ps aux|grep php
root 2269 0.0 0.1 134560 4616 ? Ss 14:27 0:00 php-fpm: master process (/etc/php/php-fpm.ini)
www-data 2270 0.2 0.2 136736 9188 ? S 14:27 0:00 php-fpm: pool www
www-data 2271 0.2 0.2 136740 9192 ? S 14:27 0:00 php-fpm: pool www
www-data 2272 0.2 0.2 134684 7284 ? S 14:27 0:00 php-fpm: pool www
www-data 2273 0.2 0.2 136732 9120 ? S 14:27 0:00 php-fpm: pool www
www-data 2274 0.1 0.2 134684 7244 ? S 14:27 0:00 php-fpm: pool www
從上面我們可以看到一個 id 為 2269 的 php-fpm 主進程 管理著 id 為 2270、2271、2272、2273、2274 的5個php-fpm 子進程
這裡需要注意的是,當並發大過start_servers數的處理能力是,備用進程才會啟動,當並發數小的時候,備用進程也會銷毀掉,所以無論什麼時候,ps 出來的進程都是上面那5個
下面來看看php-fpm+mysql的效果
mysql> show processlist;
+----+------------------+-----------+------+---------+------+----------------+-------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------------------+-----------+------+---------+------+----------------+-------------------------+
| 9 | root | localhost | NULL | Query | 0 | NULL | show processlist |
+----+------------------+-----------+------+---------+------+----------------+-------------------------+
接下來我們看短串連:
$conn = new mysqli("192.168.0.170", "redol", "redol", "test_db");
然後不斷訪問上面的php檔案,每次看到的都是
+----+---------+-----------+------+---------+------+----------------+--------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+---------+-----------+------+---------+------+----------------+--------------------+
| 9 | root | localhost | NULL | Query | 0 | NULL | show processlist |
+----+---------+-----------+------+---------+------+----------------+--------------------+
這也是php神奇的地方,居然不用close的,每次請求完了他就自己給你close掉和mysql的串連了,這點確實也讓很多新手少了不少下面的煩惱啊
Warning: mysqli::mysqli(): (HY000/1040): Too many connections in ...
不要以為語言應該都是這樣那就打錯特錯了,去看看golang吧
下面看看長串連
$conn = new mysqli("p:192.168.0.170", "redol", "redol", "test_db");
你沒看錯,mysqli的長串連和mysql不同,是在host前面加 p:,沒有mysqli_pconnet 的用法,估計很多剛開始用mysqli也是摸不著頭腦吧?
第一次訪問:
+----+-------+-------------------------+-------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------+-------------------------+-------+---------+------+-------+------------------+
| 9 | root | localhost | NULL | Query | 0 | NULL | show processlist |
| 10 | redol | bbs.demo.kkk5.com:16650 | redol | Sleep | 34 | | NULL |
+----+-------+-------------------------+-------+---------+------+-------+------------------+
重新整理一下網頁
+-----+-------+-------------------------+-------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+-------+-------------------------+-------+---------+------+-------+------------------+
| 9 | root | localhost | NULL | Query | 0 | NULL | show processlist |
| 10 | redol | bbs.demo.kkk5.com:16650 | redol | Sleep | 4 | | NULL |
| 727 | redol | bbs.demo.kkk5.com:16657 | redol | Sleep | 1 | | NULL |
+-----+-------+-------------------------+-------+---------+------+-------+------------------+
再重新整理一下網頁,效果我就不發了,反正你知道最後無論怎麼重新整理,都是如下即可
+-----+-------+-------------------------+-------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+-------+-------------------------+-------+---------+------+-------+------------------+
| 9 | root | localhost | NULL | Query | 0 | NULL | show processlist |
| 10 | redol | bbs.demo.kkk5.com:16650 | redol | Sleep | 4 | | NULL |
| 727 | redol | bbs.demo.kkk5.com:16657 | redol | Sleep | 1 | | NULL |
| 728 | redol | bbs.demo.kkk5.com:16659 | redol | Sleep | 16 | | NULL |
| 729 | redol | bbs.demo.kkk5.com:16661 | redol | Sleep | 12 | | NULL |
| 730 | redol | bbs.demo.kkk5.com:16663 | redol | Sleep | 8 | | NULL |
+-----+-------+-------------------------+-------+---------+------+-------+------------------+
也就是說,長串連是真的會一直霸佔mysql串連的,那麼問題就來了,如果我沒有重啟 php-fpm,只重啟了mysql,會出現什麼問題呢?答案是第一次串連的時候會報下面錯誤
Warning: mysqli::mysqli(): MySQL server has gone away in
所以用長串連query前還是先判斷有沒有串連,沒有就close串連,注意一定要close,再串連,否則串連是失效的。
下面我們來試試 Too many connections 的錯誤吧
先調整一下mysql的最大串連數
vim /etc/mysql/my.cnf
max_connections = 3
你沒看錯,我只給了他3個串連,而上面的php-fpm是5個,所以結果不用我說都知道了吧,如期的出現
Warning: mysqli::mysqli(): (HY000/1040): Too many connections in ...
所以線上的mysql,還是注意一下這個max_connections數吧,我只能告訴你他預設是100,如果你覺得100不夠用的話,自己改去吧
從上面可知,短串連是不用close也會自動關閉的,那如果是設定了 pm.max_requests = 2,每個php-fpm處理兩個請求就銷毀,銷毀了會close嗎?我就不截圖了,直接告訴答案吧,會的,所以無論如何,看到的mysql都是1、2、3、4、5、4、3、2、1、2、3、4、5這樣的串連數,就是慢慢增加,再慢慢減少,減少是因為php-fpm子進程銷毀了嘛
好吧,論文結束了,好好把php-fpm的原理已經mysql長短串連理解了一遍