這種方式每次都要修改crontab,如果進(jìn)程掛掉" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁(yè) > 營(yíng)銷資訊 > 網(wǎng)站運(yùn)營(yíng) > PHP開(kāi)發(fā)者該知道的多進(jìn)程消費(fèi)隊(duì)列

PHP開(kāi)發(fā)者該知道的多進(jìn)程消費(fèi)隊(duì)列

時(shí)間:2023-05-26 19:27:02 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)

時(shí)間:2023-05-26 19:27:02 來(lái)源:網(wǎng)站運(yùn)營(yíng)

PHP開(kāi)發(fā)者該知道的多進(jìn)程消費(fèi)隊(duì)列:

引言

最近開(kāi)發(fā)一個(gè)小功能,用到了隊(duì)列mcq,啟動(dòng)一個(gè)進(jìn)程消費(fèi)隊(duì)列數(shù)據(jù),后邊發(fā)現(xiàn)一個(gè)進(jìn)程處理不過(guò)來(lái)了,又加了一個(gè)進(jìn)程,過(guò)了段時(shí)間又處理不過(guò)來(lái)了…

這種方式每次都要修改crontab,如果進(jìn)程掛掉了,不會(huì)及時(shí)的啟動(dòng),要等到下次crontab執(zhí)行的時(shí)候才會(huì)啟動(dòng)。關(guān)閉(重啟)進(jìn)程的時(shí)候用的是kill,這可能會(huì)丟失正在處理的數(shù)據(jù),比如下面這個(gè)例子,我們假設(shè)sleep過(guò)程就是處理邏輯,這里為了明顯看出效果,將處理時(shí)間放大到10s:

<?php$i = 1;while (1) { echo "開(kāi)始第[{$i}]次循環(huán)/n"; sleep(10); echo "結(jié)束第[{$i}]次循環(huán)/n"; $i++;}當(dāng)我們運(yùn)行腳本之后,等到循環(huán)開(kāi)始之后,給進(jìn)程發(fā)送 kill {KaTeX parse error: Expected 'EOF', got '}' at position 4: pid}?,默認(rèn)發(fā)送的是編號(hào)為15的SI…i是從隊(duì)列拿到的,拿到2的時(shí)候,正在處理,我們給程序發(fā)送了kill信號(hào),和隊(duì)列數(shù)據(jù)丟失一樣,問(wèn)題比較大,因此我要想辦法解決這些問(wèn)題。

開(kāi)始第[1]次循環(huán)結(jié)束第[1]次循環(huán)開(kāi)始第[2]次循環(huán)[1] 28372 terminated php t.php

nginx進(jìn)程模型

這時(shí)候我想到了nginx,nginx作為高性能服務(wù)器的中流砥柱,為成千上萬(wàn)的企業(yè)和個(gè)人服務(wù),他的進(jìn)程模型比較經(jīng)典,如下所示:

管理員通過(guò)master進(jìn)程和nginx進(jìn)行交互,從/path/to/nginx.pid讀取nginx master進(jìn)程的pid,發(fā)送信號(hào)給master進(jìn)程,master根據(jù)不同的信號(hào)做出不同的處理,然后反饋信息給管理員。worker是master進(jìn)程fork出來(lái)的,master負(fù)責(zé)管理worker,不會(huì)去處理業(yè)務(wù),worker才是具體業(yè)務(wù)的處理者,master可以控制worker的退出、啟動(dòng),當(dāng)worker意外退出,master會(huì)收到子進(jìn)程退出的消息,也會(huì)重新啟動(dòng)新的worker進(jìn)程補(bǔ)充上來(lái),不讓業(yè)務(wù)處理受影響。nginx還可以平滑退出,不丟失任何一個(gè)正在處理的數(shù)據(jù),更新配置時(shí)nginx可以做到不影響線上服務(wù)來(lái)加載新的配置,這在請(qǐng)求量很大的時(shí)候特別有用。

進(jìn)程設(shè)計(jì)

看了nginx的進(jìn)模型,我們完全可以開(kāi)發(fā)一個(gè)類似的類庫(kù)來(lái)滿足處理mcq數(shù)據(jù)的需求,做到單文件控制所有進(jìn)程、可以平滑退出、可以查看子進(jìn)程狀態(tài)。不需要太復(fù)雜,因?yàn)槲覀兲幚黻?duì)列數(shù)據(jù)接收一定的延遲,做到nginx那樣不間斷服務(wù)比較麻煩,費(fèi)時(shí)費(fèi)力,意義不是很大。設(shè)計(jì)的進(jìn)程模型跟nginx類似,更像是nginx的簡(jiǎn)化版本。

進(jìn)程信號(hào)量設(shè)計(jì)

信號(hào)量是進(jìn)程間通訊的一種方式,比較簡(jiǎn)單,單功能也比較弱,只能發(fā)送信號(hào)給進(jìn)程,進(jìn)程根據(jù)信號(hào)做出不同的處理。

master進(jìn)程啟動(dòng)的時(shí)候保存pid到文件/path/to/daeminze.pid,管理員通過(guò)信號(hào)和master進(jìn)程通訊,master進(jìn)程安裝3種信號(hào),碰到不同的信號(hào),做出不同的處理,如下所示:

SIGINT => 平滑退出,處理完正在處理的數(shù)據(jù)再退出SIGTERM => 暴力退出,無(wú)論進(jìn)程是否正在處理數(shù)據(jù)直接退出SIGUSR1 => 查看進(jìn)程狀態(tài),查看進(jìn)程占用內(nèi)存,運(yùn)行時(shí)間等信息master進(jìn)程通過(guò)信號(hào)和worker進(jìn)程通訊,worker進(jìn)程安裝了2個(gè)信號(hào),如下所示:

SIGINT => 平滑退出SIGUSR1 => 查看worker進(jìn)程自身狀態(tài)為什么worker進(jìn)程只安裝2個(gè)信號(hào)呢,少了個(gè)SIGTERM,因?yàn)閙aster進(jìn)程收到信號(hào)SIGTERM之后,向worker進(jìn)程發(fā)送SIGKILL信號(hào),默認(rèn)強(qiáng)制關(guān)閉進(jìn)程即可。

worker進(jìn)程是通過(guò)master進(jìn)程fork出來(lái)的,這樣master進(jìn)程可以通過(guò)pcntl_wait來(lái)等待子進(jìn)程退出事件,當(dāng)有子進(jìn)程退出的時(shí)候返回子進(jìn)程pid,做處理并啟動(dòng)新的進(jìn)程補(bǔ)充上來(lái)。

master進(jìn)程也通過(guò)pcntl_wait來(lái)等待接收信號(hào),當(dāng)有信號(hào)到達(dá)的時(shí)候,會(huì)返回-1,這個(gè)地方還有些坑,在下文中會(huì)詳細(xì)講。

PHP中有2種信號(hào)觸發(fā)的方式,第一種方式是declare(ticks = 1);,這種效率不高,Zend每執(zhí)行一次低級(jí)語(yǔ)句,都會(huì)去檢查進(jìn)程中是否有未處理的信號(hào),現(xiàn)在已經(jīng)很少使用了,PHP 5.3.0及之前的版本可能會(huì)用到這個(gè)。

第二種是通過(guò)pcntl_signal_dispatch來(lái)調(diào)用未處理的信號(hào),PHP 5.4.0及之后的版本適用,可以巧妙的將該函數(shù)放在循環(huán)中,性能上基本沒(méi)什么損失,現(xiàn)在推薦適用。

PHP安裝修信號(hào)量

PHP通過(guò)pcntl_signal安裝信號(hào),函數(shù)聲明如下所示:

bool pcntl_signal ( int $signo , [callback $handler [, bool $restart_syscalls = true ] )第三個(gè)參數(shù)restart_syscalls不太好理解,找了很多資料,也沒(méi)太查明白,經(jīng)過(guò)試驗(yàn)發(fā)現(xiàn),這個(gè)參數(shù)對(duì)pcntl_wait函數(shù)接收信號(hào)有影響,當(dāng)設(shè)置為缺省值true的時(shí)候,發(fā)送信號(hào),進(jìn)程用pcntl_wait收不到,必須設(shè)置為false才可以,看看下面這個(gè)例子:

<?php$i = 0;while ($i<5) { $pid = pcntl_fork(); $random = rand(10, 50); if ($pid == 0) { sleep($random); exit(); } echo "child {$pid} sleep {$random}/n"; $i++;}pcntl_signal(SIGINT, function($signo) { echo "Ctrl + C/n";});while (1) { $pid = pcntl_wait($status); var_dump($pid); pcntl_signal_dispatch();}運(yùn)行之后,我們對(duì)父進(jìn)程發(fā)送kill -SIGINT {$pid}信號(hào),發(fā)現(xiàn)pcntl_wait沒(méi)有反應(yīng),等到有子進(jìn)程退出的時(shí)候,發(fā)送過(guò)的SIGINT會(huì)一個(gè)個(gè)執(zhí)行,比如下面結(jié)果:

child 29643 sleep 48child 29644 sleep 24child 29645 sleep 37child 29646 sleep 20child 29647 sleep 31int(29643)Ctrl + CCtrl + CCtrl + CCtrl + Cint(29646)這是運(yùn)行腳本之后馬上給父進(jìn)程發(fā)送了四次SIGINT信號(hào),等到一個(gè)子進(jìn)程推出的時(shí)候,所有信號(hào)都會(huì)觸發(fā)。

但當(dāng)把安裝信號(hào)的第三個(gè)參數(shù)設(shè)置為false:

pcntl_signal(SIGINT, function($signo) { echo "Ctrl + C/n";}, false);這時(shí)候給父進(jìn)程發(fā)送SIGINT信號(hào),pcntl_wait會(huì)馬上返回-1,信號(hào)對(duì)應(yīng)的事件也會(huì)觸發(fā)。

所以第三個(gè)參數(shù)大概意思就是,是否重新注冊(cè)此信號(hào),如果為false只注冊(cè)一次,觸發(fā)之后就返回,pcntl_wait就能收到消息,如果為true,會(huì)重復(fù)注冊(cè),不會(huì)返回,pcntl_wait收不到消息。

信號(hào)量和系統(tǒng)調(diào)用

信號(hào)量會(huì)打斷系統(tǒng)調(diào)用,讓系統(tǒng)調(diào)用立刻返回,比如sleep,當(dāng)進(jìn)程正在sleep的時(shí)候,收到信號(hào),sleep會(huì)馬上返回剩余sleep秒數(shù),比如:

<?phppcntl_signal(SIGINT, function($signo) { echo "Ctrl + C/n";}, false);while (true) { pcntl_signal_dispatch(); echo "123/n"; $limit = sleep(2); echo "limit sleep [{$limit}] s/n";}運(yùn)行之后,按Ctrl + C,結(jié)果如下所示:

123^Climit sleep [1] sCtrl + C123limit sleep [0] s123^Climit sleep [1] sCtrl + C123^Climit sleep [2] s

daemon(守護(hù))進(jìn)程

這種進(jìn)程一般設(shè)計(jì)為daemon進(jìn)程,不受終端控制,不與終端交互,長(zhǎng)時(shí)間運(yùn)行在后臺(tái),而對(duì)于一個(gè)進(jìn)程,我們可以通過(guò)下面幾個(gè)步驟把他升級(jí)為一個(gè)標(biāo)準(zhǔn)的daemon進(jìn)程:

protected function daemonize(){ $pid = pcntl_fork(); if (-1 == $pid) { throw new Exception("fork進(jìn)程失敗"); } elseif ($pid != 0) { exit(0); } if (-1 == posix_setsid()) { throw new Exception("新建立session會(huì)話失敗"); } $pid = pcntl_fork(); if (-1 == $pid) { throw new Exception("fork進(jìn)程失敗"); } else if($pid != 0) { exit(0); } umask(0); chdir("/");}攏共分五步:

  1. fork子進(jìn)程,父進(jìn)程退出。
  2. 設(shè)置子進(jìn)程為會(huì)話組長(zhǎng),進(jìn)程組長(zhǎng)。
  3. 再次fork,父進(jìn)程退出,子進(jìn)程繼續(xù)運(yùn)行。
  4. 恢復(fù)文件掩碼為0。
  5. 切換當(dāng)前目錄到根目錄/。
第2步是為第1步做準(zhǔn)備,設(shè)置進(jìn)程為會(huì)話組長(zhǎng),必要條件是進(jìn)程非進(jìn)程組長(zhǎng),因此做第一次fork,進(jìn)程組長(zhǎng)(父進(jìn)程)退出,子進(jìn)程通過(guò)posix_setsid()設(shè)置為會(huì)話組長(zhǎng),同時(shí)也為進(jìn)程組長(zhǎng)。

第3步是為了不讓進(jìn)程重新控制終端,因?yàn)橐粋€(gè)進(jìn)程控制一個(gè)終端的必要條件是會(huì)話組長(zhǎng)(pid=sid)。

第4步是為了恢復(fù)默認(rèn)的文件掩碼,避免之前做的操作對(duì)文件掩碼做了設(shè)置,帶來(lái)不必要的麻煩。關(guān)于文件掩碼, linux中,文件掩碼在創(chuàng)建文件、文件夾的時(shí)候會(huì)用到,文件的默認(rèn)權(quán)限為666,文件夾為777,創(chuàng)建文件(夾)的時(shí)候會(huì)用默認(rèn)值減去掩碼的值作為創(chuàng)建文件(夾)的最終值,比如掩碼022下創(chuàng)建文件666 - 222 = 644,創(chuàng)建文件夾777 - 022 = 755:

第5步是切換了當(dāng)前目錄到根目錄/,網(wǎng)上說(shuō)避免起始運(yùn)行他的目錄不能被正確卸載,這個(gè)不是太了解。

對(duì)應(yīng)5步,每一步的各種id變化信息:

另外,會(huì)話、進(jìn)程組、進(jìn)程的關(guān)系如下圖所示,這張圖有助于更好的理解。

至此,你也可以輕松地造出一個(gè)daemon進(jìn)程了。

命令設(shè)計(jì)

我準(zhǔn)備給這個(gè)類庫(kù)設(shè)計(jì)6個(gè)命令,如下所示:

  1. start 啟動(dòng)命令
  2. restart 強(qiáng)制重啟
  3. stop 平滑停止
  4. reload 平滑重啟
  5. quit 強(qiáng)制停止
  6. status 查看進(jìn)程狀態(tài)

啟動(dòng)命令

啟動(dòng)命令就是默認(rèn)的流程,按照默認(rèn)流程走就是啟動(dòng)命令,啟動(dòng)命令會(huì)檢測(cè)pid文件中是否已經(jīng)有pid,pid對(duì)應(yīng)的進(jìn)程是否健康,是否需要重新啟動(dòng)。

強(qiáng)制停止命令

管理員通過(guò)入口文件結(jié)合pid給master進(jìn)程發(fā)送SIGTERM信號(hào),master進(jìn)程給所有子進(jìn)程發(fā)送SIGKILL信號(hào),等待所有worker進(jìn)程退出后,master進(jìn)程也退出。

強(qiáng)制重啟命令

強(qiáng)制停止命令 + 啟動(dòng)命令

平滑停止命令

平滑停止命令,管理員給master進(jìn)程發(fā)送SIGINT信號(hào),master進(jìn)程給所有子進(jìn)程發(fā)送SIGINT,worker進(jìn)程將自身狀態(tài)標(biāo)記為stoping,當(dāng)worker進(jìn)程下次循環(huán)的時(shí)候會(huì)根據(jù)stoping決定停止,不在接收新的數(shù)據(jù),等所有worker進(jìn)程退出之后,master進(jìn)程也退出。

平滑重啟命令

平滑停止命令 + 啟動(dòng)命令

查看進(jìn)程狀態(tài)

查看進(jìn)程狀態(tài)這個(gè)借鑒了workerman的思路,管理員給master進(jìn)程發(fā)送SIGUSR1信號(hào),告訴主進(jìn)程,我要看所有進(jìn)程的信息,master進(jìn)程,master進(jìn)程將自身的進(jìn)程信息寫入配置好的文件路徑A中,然后發(fā)送SIGUSR1,告訴worker進(jìn)程把自己的信息也寫入文件A中,由于這個(gè)過(guò)程是異步的,不知道worker進(jìn)程啥時(shí)候?qū)懲?,所以master進(jìn)程在此處等待,等所有worker進(jìn)程都寫入文件之后,格式化所有的信息輸出,最后輸出的內(nèi)容如下所示:

?/dir /usr/local/bin/php DaemonMcn.php statusDaemon [DaemonMcn] 信息:-------------------------------- master進(jìn)程狀態(tài) --------------------------------pid 占用內(nèi)存 處理次數(shù) 開(kāi)始時(shí)間 運(yùn)行時(shí)間16343 0.75M -- 2018-05-15 09:42:45 0 天 0 時(shí) 3 分12 slaver-------------------------------- slaver進(jìn)程狀態(tài) --------------------------------任務(wù)task-mcq:16345 0.75M 236 2018-05-15 09:42:45 0 天 0 時(shí) 3 分16346 0.75M 236 2018-05-15 09:42:45 0 天 0 時(shí) 3 分--------------------------------------------------------------------------------任務(wù)test-mcq:16348 0.75M 49 2018-05-15 09:42:45 0 天 0 時(shí) 3 分16350 0.75M 49 2018-05-15 09:42:45 0 天 0 時(shí) 3 分16358 0.75M 49 2018-05-15 09:42:45 0 天 0 時(shí) 3 分16449 0.75M 1 2018-05-15 09:46:40 0 天 0 時(shí) 0 分--------------------------------------------------------------------------------等待worker進(jìn)程將進(jìn)程信息寫入文件的時(shí)候,這個(gè)地方用了個(gè)比較trick的方法,每個(gè)worker進(jìn)程輸出一行信息,統(tǒng)計(jì)文件的行數(shù),達(dá)到worker進(jìn)程的行數(shù)之后表示所有worker進(jìn)程都將信息寫入完畢,否則,每個(gè)1s檢測(cè)一次。

其他設(shè)計(jì)

另外還加了兩個(gè)比較實(shí)用的功能,一個(gè)是worker進(jìn)程運(yùn)行時(shí)間限制,一個(gè)是worker進(jìn)程循環(huán)處理次數(shù)限制,防止長(zhǎng)時(shí)間循環(huán)進(jìn)程出現(xiàn)內(nèi)存溢出等意外情況。時(shí)間默認(rèn)是1小時(shí),運(yùn)行次數(shù)默認(rèn)是10w次。

除此之外,也可以支持多任務(wù),每個(gè)任務(wù)幾個(gè)進(jìn)程獨(dú)立開(kāi),統(tǒng)一由master進(jìn)程管理。

以上內(nèi)容希望幫助到大家,很多PHPer在進(jìn)階的時(shí)候總會(huì)遇到一些問(wèn)題和瓶頸,業(yè)務(wù)代碼寫多了沒(méi)有方向感,不知道該從那里入手去提升,對(duì)此我整理了一些資料,包括但不限于:分布式架構(gòu)、高可擴(kuò)展、高性能、高并發(fā)、服務(wù)器性能調(diào)優(yōu)、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優(yōu)化、shell腳本、Docker、微服務(wù)、Nginx等多個(gè)知識(shí)點(diǎn)高級(jí)進(jìn)階干貨需要的可以免費(fèi)分享給大家,需要的可以加入我的PHP技術(shù)交流群953224940



關(guān)鍵詞:進(jìn)程,消費(fèi)

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉