亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解PHP多進(jìn)程消費(fèi)隊列

 更新時間:2021年06月04日 10:05:05   作者:李留廣  
本文詳細(xì)介紹了PHP多進(jìn)程消費(fèi)隊列,感興趣的同學(xué),可以參考下,理解其原理。

引言

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

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

<?php
$i = 1;
while (1) {
    echo "開始第[{$i}]次循環(huán)\n";
    sleep(10);
    echo "結(jié)束第[{$i}]次循環(huán)\n";
    $i++;
}

當(dāng)我們運(yùn)行腳本之后,等到循環(huán)開始之后,給進(jìn)程發(fā)送kill {$pid},默認(rèn)發(fā)送的是編號為15的SIGTERM信號。假設(shè)$i是從隊列拿到的,拿到2的時候,正在處理,我們給程序發(fā)送了kill信號,和隊列數(shù)據(jù)丟失一樣,問題比較大,因此我要想辦法解決這些問題。

開始第[1]次循環(huán)

結(jié)束第[1]次循環(huán)

開始第[2]次循環(huán)

[1]    28372 terminated  php t.php

nginx進(jìn)程模型

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

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

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

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

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

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

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

SIGINT => 平滑退出,處理完正在處理的數(shù)據(jù)再退出

SIGTERM => 暴力退出,無論進(jìn)程是否正在處理數(shù)據(jù)直接退出

SIGUSR1 => 查看進(jìn)程狀態(tài),查看進(jìn)程占用內(nèi)存,運(yùn)行時間等信息

master進(jìn)程通過信號和worker進(jìn)程通訊,worker進(jìn)程安裝了2個信號,如下所示:

SIGINT => 平滑退出

SIGUSR1 => 查看worker進(jìn)程自身狀態(tài)

為什么worker進(jìn)程只安裝2個信號呢,少了個SIGTERM,因?yàn)閙aster進(jìn)程收到信號SIGTERM之后,向worker進(jìn)程發(fā)送SIGKILL信號,默認(rèn)強(qiáng)制關(guān)閉進(jìn)程即可。

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

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

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

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

PHP安裝修信號量

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

bool pcntl_signal ( int $signo , [callback $handler [, bool $restart_syscalls = true ] )

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

<?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)行之后,我們對父進(jìn)程發(fā)送kill -SIGINT {$pid}信號,發(fā)現(xiàn)pcntl_wait沒有反應(yīng),等到有子進(jìn)程退出的時候,發(fā)送過的SIGINT會一個個執(zhí)行,比如下面結(jié)果:

child 29643 sleep 48

child 29644 sleep 24

child 29645 sleep 37

child 29646 sleep 20

child 29647 sleep 31

int(29643)

Ctrl + C

Ctrl + C

Ctrl + C

Ctrl + C

int(29646)

這是運(yùn)行腳本之后馬上給父進(jìn)程發(fā)送了四次SIGINT信號,等到一個子進(jìn)程推出的時候,所有信號都會觸發(fā)。

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

pcntl_signal(SIGINT,  function($signo) {
     echo "Ctrl + C\n";
}, false);

這時候給父進(jìn)程發(fā)送SIGINT信號,pcntl_wait會馬上返回-1,信號對應(yīng)的事件也會觸發(fā)。

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

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

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

<?php
pcntl_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] s

Ctrl + C

123

limit sleep [0] s

123

^Climit sleep [1] s

Ctrl + C

123

^Climit sleep [2] s

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

這種進(jìn)程一般設(shè)計為daemon進(jìn)程,不受終端控制,不與終端交互,長時間運(yùn)行在后臺,而對于一個進(jìn)程,我們可以通過下面幾個步驟把他升級為一個標(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會話失敗");
    }

    $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)程為會話組長,進(jìn)程組長。

3.再次fork,父進(jìn)程退出,子進(jìn)程繼續(xù)運(yùn)行。

4.恢復(fù)文件掩碼為0。

5.切換當(dāng)前目錄到根目錄/。

第2步是為第1步做準(zhǔn)備,設(shè)置進(jìn)程為會話組長,必要條件是進(jìn)程非進(jìn)程組長,因此做第一次fork,進(jìn)程組長(父進(jìn)程)退出,子進(jìn)程通過posix_setsid()設(shè)置為會話組長,同時也為進(jìn)程組長。

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

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

掩碼 新建文件權(quán)限 新建文件夾權(quán)限
umask(0) 666 (-rw-rw-rw-) 777 (drwxrwxrwx)
umask(022) 644 (-rw-r--r--) 755 (drwxr-xr-x)

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

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

操作后 pid ppid pgid sid
開始 17723 31381 17723 31381
第一次fork 17723 1 17723 31381
posix_setsid() 17740 1 17740 17740
第二次fork 17840 1 17740 17740

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

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

命令設(shè)計

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

1.start 啟動命令

2.restart 強(qiáng)制重啟

3.stop 平滑停止

4.reload 平滑重啟

5.quit 強(qiáng)制停止

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

啟動命令

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

強(qiáng)制停止命令

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

強(qiáng)制重啟命令

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

平滑停止命令

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

平滑重啟命令

平滑停止命令+啟動命令

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

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

➜/dir /usr/local/bin/php DaemonMcn.php status

Daemon [DaemonMcn] 信息:

-------------------------------- master進(jìn)程狀態(tài) --------------------------------

pid       占用內(nèi)存       處理次數(shù)       開始時間                 運(yùn)行時間

16343     0.75M          --             2018-05-15 09:42:45      0 天 0 時 3 分

12 slaver

-------------------------------- slaver進(jìn)程狀態(tài) --------------------------------

任務(wù)task-mcq:

16345     0.75M          236            2018-05-15 09:42:45      0 天 0 時 3 分

16346     0.75M          236            2018-05-15 09:42:45      0 天 0 時 3 分

--------------------------------------------------------------------------------

任務(wù)test-mcq:

16348     0.75M          49             2018-05-15 09:42:45      0 天 0 時 3 分

16350     0.75M          49             2018-05-15 09:42:45      0 天 0 時 3 分

16358     0.75M          49             2018-05-15 09:42:45      0 天 0 時 3 分

16449     0.75M          1              2018-05-15 09:46:40      0 天 0 時 0 分

--------------------------------------------------------------------------------

等待worker進(jìn)程將進(jìn)程信息寫入文件的時候,這個地方用了個比較trick的方法,每個worker進(jìn)程輸出一行信息,統(tǒng)計文件的行數(shù),達(dá)到worker進(jìn)程的行數(shù)之后表示所有worker進(jìn)程都將信息寫入完畢,否則,每個1s檢測一次。

以上就是詳解PHP多進(jìn)程消費(fèi)隊列的詳細(xì)內(nèi)容,更多關(guān)于PHP多進(jìn)程消費(fèi)隊列的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論