PHP中的“進(jìn)程”系列
這個(gè)系列會(huì)分幾個(gè)部分,從PHP-FPM進(jìn)程模式起锭环,到Linux進(jìn)程禾酱,最后回到PHP本身談一談如何設(shè)計(jì)一個(gè)PHP的進(jìn)程池。整個(gè)系列會(huì)氛圍大致5個(gè)主要部分霉赡,分別是:
①:PHP-FPM的多進(jìn)程模型
②:Linux進(jìn)程介紹
③:PHP中的多進(jìn)程
④:進(jìn)程間通訊
⑤:PHP的進(jìn)程池設(shè)計(jì)
此篇為系列第一篇:PHP-FPM的多進(jìn)程模型橄务。那么,我們談?wù)揚(yáng)HP-FPM多進(jìn)程模型的時(shí)候穴亏,作為PHPer的你蜂挪,可能需要先看看下面一些關(guān)于PHP-FPM的多進(jìn)程模型,是否都有所了解
①:PHP-FPM啟動(dòng)進(jìn)程的方式主要有哪幾種嗓化,區(qū)別是什么棠涮?
②:PHP-FPM,是主進(jìn)程接收請(qǐng)求轉(zhuǎn)給子進(jìn)程刺覆,還是子進(jìn)程單獨(dú)接收請(qǐng)求并處理严肪,如何驗(yàn)證?
③:為何在PHP-FPM模式下,PHP代碼很少有人去做連接池驳糯?
④:PHP-FPM模式性能差的體現(xiàn)有哪些篇梭,如何優(yōu)化?
⑤:PHP-FPM模式下的Yac為何無(wú)法和Cli模式無(wú)法共享內(nèi)存酝枢?
1很洋、PHP-FPM是多進(jìn)程模式,master進(jìn)程管理worker進(jìn)程隧枫,進(jìn)程的數(shù)量喉磁,都可以通過(guò)php-fpm.conf做具體配置,而PHP-FPM的進(jìn)程官脓,亦可以分為動(dòng)態(tài)模式及靜態(tài)模式协怒。
①:靜態(tài)(static):直接開(kāi)啟指定數(shù)量的php-fpm進(jìn)程,不再增加或者減少卑笨;啟動(dòng)固定數(shù)量的進(jìn)程孕暇,占用內(nèi)存高。但在用戶(hù)請(qǐng)求波動(dòng)大的時(shí)候赤兴,對(duì)Linux操作系統(tǒng)進(jìn)程的處理上耗費(fèi)的系統(tǒng)資源低妖滔。
②:動(dòng)態(tài)(dynamic):開(kāi)始的時(shí)候開(kāi)啟一定數(shù)量的php-fpm進(jìn)程,當(dāng)請(qǐng)求量變大的時(shí)候桶良,動(dòng)態(tài)的增加php-fpm進(jìn)程數(shù)到上限座舍,當(dāng)空閑的時(shí)候自動(dòng)釋放空閑的進(jìn)程數(shù)到一個(gè)下限。動(dòng)態(tài)模式陨帆,會(huì)根據(jù)max曲秉、min、idle children 配置疲牵,動(dòng)態(tài)的調(diào)整進(jìn)程數(shù)量承二。在用戶(hù)請(qǐng)求較為波動(dòng),或者瞬間請(qǐng)求增高的時(shí)候纲爸,進(jìn)行大量進(jìn)程的創(chuàng)建亥鸠、銷(xiāo)毀等操作,而造成Linux負(fù)載波動(dòng)升高识啦,簡(jiǎn)單來(lái)說(shuō)负蚊,請(qǐng)求量少,PHP-FPM進(jìn)程數(shù)少袁滥,請(qǐng)求量大盖桥,進(jìn)程數(shù)多灾螃。優(yōu)勢(shì)就是题翻,當(dāng)請(qǐng)求量小的時(shí)候,進(jìn)程數(shù)少,內(nèi)存占用也小嵌赠。
③:按需模式(ondemand):這種模式下塑荒,PHP-FPM的master不會(huì)fork任何的子進(jìn)程,純粹就是按需啟動(dòng)子進(jìn)程姜挺,這種模式很少使用齿税,因?yàn)檫@種模式,基本上是無(wú)法適應(yīng)有一定量級(jí)的線上業(yè)務(wù)的炊豪。由于php-fpm是短連接的凌箕,所以每次請(qǐng)求都會(huì)先建立連接,建立連接的過(guò)程必然會(huì)觸發(fā)上圖的執(zhí)行步驟词渤,所以牵舱,在大流量的系統(tǒng)上master進(jìn)程會(huì)變得繁忙,占用系統(tǒng)cpu資源缺虐,不適合大流量環(huán)境的部署芜壁。這種模式,貼一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)上的圖來(lái)說(shuō)明:
需要注意2個(gè)點(diǎn)高氮,“連接”慧妄,及“數(shù)據(jù)”到來(lái)。有連接進(jìn)來(lái)再fork進(jìn)程剪芍,同樣可以達(dá)到子進(jìn)程繼承父進(jìn)程上下文塞淹,然后子進(jìn)程處理用戶(hù)請(qǐng)求這個(gè)目的。
具體的罪裹,關(guān)于動(dòng)態(tài)窖铡、靜態(tài)進(jìn)程模式的相關(guān)參數(shù),可參考PHP官方文檔坊谁,我們需要關(guān)注的是费彼,對(duì)于我們自身的業(yè)務(wù),如何選擇PHP-FPM的模式為動(dòng)態(tài)還是靜態(tài)口芍。
比較大內(nèi)存的服務(wù)器來(lái)說(shuō)箍铲,設(shè)置為靜態(tài)的話會(huì)提高效率。因?yàn)轭l繁開(kāi)關(guān)php-fpm進(jìn)程也會(huì)有時(shí)滯鬓椭,所以?xún)?nèi)存夠大的情況下開(kāi)靜態(tài)效果會(huì)更好颠猴。數(shù)量也可以根據(jù) 內(nèi)存/30M 得到。比如說(shuō)2GB內(nèi)存的服務(wù)器小染,可以設(shè)置為50翘瓮;4GB內(nèi)存可以設(shè)置為100等。高配機(jī)器選靜態(tài)裤翩,低配機(jī)器(省內(nèi)存)選動(dòng)態(tài)资盅,高配機(jī)器用動(dòng)態(tài)不能充分利用內(nèi)存資源和CPU資源,也無(wú)法及時(shí)應(yīng)對(duì)瞬時(shí)高并發(fā),甚至可能短時(shí)間造成5xx錯(cuò)誤呵扛。
2每庆、PHP-FPM,是主進(jìn)程接收請(qǐng)求轉(zhuǎn)給子進(jìn)程今穿,還是子進(jìn)程單獨(dú)接收請(qǐng)求并處理缤灵,如何驗(yàn)證
PHP-FPM的進(jìn)程管理方式和Nginx的進(jìn)程管理方式類(lèi)似,在處理用于請(qǐng)求上蓝晒,并非是主進(jìn)程接受請(qǐng)求后轉(zhuǎn)給子進(jìn)程腮出,而是子進(jìn)程搶占式的接受用戶(hù)的請(qǐng)求,本質(zhì)上芝薇,其實(shí)PHP-FPM的多進(jìn)程利诺,以及Nginx的多進(jìn)程,其實(shí)都是主進(jìn)程監(jiān)聽(tīng)的同一個(gè)端口(被動(dòng)套接字)后剩燥,fork子進(jìn)程達(dá)到多個(gè)進(jìn)程監(jiān)聽(tīng)同一個(gè)端口的目的慢逾。 Linux系統(tǒng),所有的進(jìn)程IO操作灭红,都需要和操作系統(tǒng)打交道侣滩,也就是說(shuō),所有IO操作变擒,操作系統(tǒng)都知道君珠,而這個(gè)過(guò)程,也就是我們常說(shuō)的“系統(tǒng)調(diào)用”娇斑。我們可以從系統(tǒng)調(diào)用入手解決這個(gè)問(wèn)題策添。 系統(tǒng)調(diào)用的查看,可以使用strace毫缆。
對(duì)于如何驗(yàn)證相對(duì)簡(jiǎn)單唯竹,有2種方式;其一苦丁,看php-fpm進(jìn)程的日志浸颓,這需要配置好合適的php-fpm日志格式;其二旺拉,既然IO數(shù)據(jù)會(huì)通過(guò)內(nèi)核態(tài)過(guò)度到用戶(hù)態(tài)進(jìn)程产上,那么,我們通過(guò)strace -p <pid>命令去跟蹤系統(tǒng)調(diào)用即可蛾狗。分別跟蹤php-fpm的主進(jìn)程id以及php-fpm子進(jìn)程id晋涣,然后訪問(wèn)nginx,由nginx通過(guò)fast-cgi協(xié)議轉(zhuǎn)到php-fpm進(jìn)程上沉桌,看在哪個(gè)進(jìn)程上發(fā)送了系統(tǒng)調(diào)用谢鹊。
3算吩、為何在PHP-FPM模式下,PHP代碼很少有人去做連接池
首先撇贺,PHP-FPM模式下赌莺,注定一個(gè)請(qǐng)求的生命周期只有1次冰抢。也就是說(shuō)松嘶,從FPM請(qǐng)求到請(qǐng)求,解析PHP腳本挎扰,F(xiàn)PM的Zend虛擬機(jī)分配資源執(zhí)行翠订,到最后的處理結(jié)束,PHP-FPM會(huì)回收這次請(qǐng)求的所有資源遵倦。
當(dāng)然尽超,PHP-FPM之所以這么做,①:目的是讓開(kāi)發(fā)不需要關(guān)心資源的回收的處理梧躺,所以可能你沒(méi)怎么關(guān)心過(guò)網(wǎng)絡(luò)的關(guān)閉似谁、文件描述符的關(guān)閉等等。②:減少內(nèi)存溢出的情況掠哥。
如果在這種模式下巩踏,你實(shí)現(xiàn)了連接池,也意味著請(qǐng)求結(jié)束续搀,連接池消失塞琼,做了一次無(wú)用功而已。
“雞肋的”pconnect禁舷。pconnect彪杉,持久化鏈接,也就是鏈接不釋放牵咙。但問(wèn)題在于派近,PHP-FPM是多進(jìn)程模式,而持久化的鏈接洁桌,存在于進(jìn)程中构哺,也就意味著,如果一臺(tái)機(jī)器有300個(gè)FPM進(jìn)程战坤,會(huì)一次性初始化300個(gè)持久化鏈接曙强。 如果因?yàn)槊媾R業(yè)務(wù)活動(dòng),冒然對(duì)機(jī)器擴(kuò)容途茫,很可能造成業(yè)務(wù)的數(shù)據(jù)庫(kù)連接數(shù)直接打滿碟嘴。
4、PHP-FPM模式性能差的體現(xiàn)有哪些囊卜,如何優(yōu)化
先思考為何性能差娜扇,一個(gè)應(yīng)用的性能如果說(shuō)差错沃,往往會(huì)從2個(gè)方面來(lái)說(shuō),一個(gè)是IO性能雀瓢,一個(gè)是計(jì)算性能枢析。
IO上來(lái)說(shuō),PHP-FPM模式下刃麸,難以做連接池醒叁,所以高并發(fā)業(yè)務(wù)下,網(wǎng)絡(luò)的處理會(huì)有劣勢(shì)泊业。 注意:我這里一直在說(shuō)的把沼,都是 PHP-FPM模式下,在CLI模式下吁伺,你還是可以做自己的連接池的饮睬,只不過(guò)這個(gè)連接池,僅限于CLI模式的單進(jìn)程內(nèi)篮奄,這個(gè)模式還不能用在處理網(wǎng)絡(luò)請(qǐng)求(比如HTTP請(qǐng)求)捆愁,因?yàn)镻HP默認(rèn)單進(jìn)程模式,F(xiàn)PM窟却、CLI都是默認(rèn)單進(jìn)程昼丑,即便CLI可以做連接池,也不方便做鏈接奔湫#活(不能同時(shí)做心跳檢測(cè))
計(jì)算性能上來(lái)說(shuō)矾克,其實(shí)PHP是C寫(xiě)的,單純的論計(jì)算性能是不錯(cuò)的憔足。 但問(wèn)題在于胁附,PHP在處理請(qǐng)求的時(shí)候,每次都要解析PHP腳本滓彰、翻譯PHP代碼為opcode控妻、用Zend虛擬機(jī)執(zhí)行opcode,處理結(jié)束揭绑,釋放資源弓候。因此算下來(lái),也是PHP慢的最大原因之一他匪。
如何優(yōu)化:
①:對(duì)于計(jì)算性能來(lái)說(shuō)菇存,使用 Zend OPcache 擴(kuò)展,緩存字節(jié)碼邦蜜。
②:對(duì)于IO性能來(lái)說(shuō)依鸥,使用文件cache或者memcached減輕對(duì)網(wǎng)絡(luò)Cache的壓力;使用 Yac 減輕對(duì) Cache層的壓力悼沈;在同一次請(qǐng)求中贱迟;復(fù)用鏈接不要每次都用新的姐扮;合理設(shè)計(jì)日志組件類(lèi)庫(kù),優(yōu)化Logger減少對(duì)文件操作的次數(shù)來(lái)減少I(mǎi)O的壓力衣吠。
關(guān)于設(shè)計(jì)一個(gè)合格的Logger組件茶敏,我們需要注意幾個(gè)點(diǎn):
①:每次請(qǐng)求,只做一次日志寫(xiě)操作缚俏,不要每次別人調(diào)用你的函數(shù)惊搏,你都去執(zhí)行一次類(lèi)似file_put_contents的操作。
②:兼容各種類(lèi)似錯(cuò)誤袍榆,換句話說(shuō)胀屿,即使PHP fatal error了塘揣,你也得能把知名錯(cuò)誤之前的日志記錄下來(lái)包雀。這個(gè)實(shí)現(xiàn),可以借助PHP類(lèi)的析構(gòu)方法來(lái)做亲铡。也可以使用更好的 register_shutdown_function 來(lái)注冊(cè)一個(gè)鉤子才写,在PHP請(qǐng)求結(jié)束的時(shí)候,回調(diào)此鉤子奖蔓,完成做最后的日志操作赞草。
5、PHP-FPM模式下的Yac為何無(wú)法和Cli模式無(wú)法共享內(nèi)存
我們知道吆鹤,PHP擴(kuò)展開(kāi)發(fā)中厨疙,首要執(zhí)行的一個(gè)宏,便是 PHP_MINIT_FUNCTION疑务,Yac擴(kuò)展沾凄,需要在PHP-FPM進(jìn)程啟動(dòng)的時(shí)候,便初始化一塊共享內(nèi)存知允,供各個(gè)進(jìn)程來(lái)共享使用撒蟀,因此,要能共享温鸽,關(guān)鍵就在于需要一個(gè)相同的標(biāo)識(shí)保屯,各個(gè)進(jìn)程都知道才可以。Yac擴(kuò)展的初始化流程為:
PHP_MINIT_FUNCTION->yac_storage_startup->yac_allocator_startup->create_segments
我們查看 create_segments 的具體實(shí)現(xiàn):
static int create_segments(size_t requested_size, zend_shared_segment_posix ***shared_segments_p, int *shared_segments_count, char **error_in)
{
zend_shared_segment_posix *shared_segment;
char shared_segment_name[sizeof("/ZendAccelerator.") + 20];
*shared_segments_count = 1;
*shared_segments_p = (zend_shared_segment_posix **) calloc(1, sizeof(zend_shared_segment_posix) + sizeof(void *));
if (!*shared_segments_p) {
*error_in = "calloc";
return ALLOC_FAILURE;
}
shared_segment = (zend_shared_segment_posix *)((char *)(*shared_segments_p) + sizeof(void *));
(*shared_segments_p)[0] = shared_segment;
// 這里打開(kāi)共享內(nèi)存塊需要的Id涤垫,也就是 shared_segment_name
sprintf(shared_segment_name, "/ZendAccelerator.%d", getpid());
// 這里姑尺,打開(kāi)一塊共享內(nèi)存
shared_segment->shm_fd = shm_open(shared_segment_name, O_RDWR|O_CREAT|O_TRUNC, 0600);
if (shared_segment->shm_fd == -1) {
*error_in = "shm_open";
return ALLOC_FAILURE;
}
上面做了一些注釋?zhuān)铌P(guān)鍵的是開(kāi)啟共享內(nèi)存需要的系統(tǒng)ID,shared_segment_name蝠猬,此值切蟋,包含了進(jìn)程的ID。也就是php-fpm的主進(jìn)程id吱雏。這就是敦姻,PHP-FPM模式所有進(jìn)程間能夠通信的奧秘所在(它們有相同的共享內(nèi)存標(biāo)識(shí)ID)瘾境。而,如果我們是想要通過(guò)PHP腳本镰惦,使用yac擴(kuò)展讀取這個(gè)共享內(nèi)存迷守,會(huì)這樣做:
$yac = new Yac();
$key = "something"
$yac->get($key);
在CLI模式下,這樣是不可能拿到PHP-FPM模式下設(shè)置的共享內(nèi)存數(shù)據(jù)的因?yàn)橥耄驗(yàn)镃LI模式下兑凿,執(zhí)行php腳本,進(jìn)程ID茵瘾,和PHP-FPM模式下的進(jìn)程ID礼华,根本就不相同。
總結(jié)來(lái)說(shuō)拗秘,在后邊會(huì)講到進(jìn)程間通訊圣絮,會(huì)講到基于共享內(nèi)存的通訊。多進(jìn)程要共享內(nèi)存通信雕旨,必須要一開(kāi)始就協(xié)調(diào)好一個(gè)唯一ID扮匠,這個(gè)ID,多個(gè)進(jìn)程間都要知道凡涩,PHP-FPM是多進(jìn)程棒搜,主進(jìn)程fork子進(jìn)程出來(lái),子進(jìn)程自然知道這個(gè)唯一ID是什么(因?yàn)長(zhǎng)inux進(jìn)程fork會(huì)把整個(gè)進(jìn)程的堆棧內(nèi)存都fork一遍)活箕。 但是力麸,php a.php 這樣執(zhí)行,其實(shí)是一個(gè)完全獨(dú)立的進(jìn)程育韩,和php-fpm沒(méi)任何關(guān)系克蚂,這樣的進(jìn)程,自然不能知道php-fpm進(jìn)程里的那個(gè)唯一ID是什么座慰。
至此陨舱,系列1——PHP-FPM模式已結(jié)束~