站長資訊網
        最全最豐富的資訊網站

        淺談PHP中的多進程消費隊列

        本篇文章帶大家了解一下PHP中的多進程消費隊列。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有所幫助。

        淺談PHP中的多進程消費隊列

        推薦學習:《PHP視頻教程》

        最近開發一個小功能,用到了隊列mcq,啟動一個進程消費隊列數據,后邊發現一個進程處理不過來了,又加了一個進程,過了段時間又處理不過來了……

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

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

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

        開始第[1]次循環 結束第[1]次循環 開始第[2]次循環   [1]    28372 terminated  php t.php

        nginx進程模型

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

        淺談PHP中的多進程消費隊列

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

        進程設計

        看了nginx的進模型,我們完全可以開發一個類似的類庫來滿足處理mcq數據的需求,做到單文件控制所有進程、可以平滑退出、可以查看子進程狀態。不需要太復雜,因為我們處理隊列數據接收一定的延遲,做到nginx那樣不間斷服務比較麻煩,費時費力,意義不是很大。設計的進程模型跟nginx類似,更像是nginx的簡化版本。
        淺談PHP中的多進程消費隊列

        進程信號量設計

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

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

        SIGINT 	=> 平滑退出,處理完正在處理的數據再退出 SIGTERM => 暴力退出,無論進程是否正在處理數據直接退出 SIGUSR1 => 查看進程狀態,查看進程占用內存,運行時間等信息

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

        SIGINT 	=> 平滑退出 SIGUSR1	=> 查看worker進程自身狀態

        為什么worker進程只安裝2個信號呢,少了個SIGTERM,因為master進程收到信號SIGTERM之后,向worker進程發送SIGKILL信號,默認強制關閉進程即可。

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

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

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

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

        PHP安裝修信號量

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

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

        第三個參數restart_syscalls不太好理解,找了很多資料,也沒太查明白,經過試驗發現,這個參數對pcntl_wait函數接收信號有影響,當設置為缺省值true的時候,發送信號,進程用pcntl_wait收不到,必須設置為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 + Cn"; });  while (1) {     $pid = pcntl_wait($status);     var_dump($pid);     pcntl_signal_dispatch(); }

        運行之后,我們對父進程發送kill -SIGINT {$pid}信號,發現pcntl_wait沒有反應,等到有子進程退出的時候,發送過的SIGINT會一個個執行,比如下面結果:

        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)

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

        但當把安裝信號的第三個參數設置為false

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

        這時候給父進程發送SIGINT信號,pcntl_wait會馬上返回-1,信號對應的事件也會觸發。

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

        信號量和系統調用

        信號量會打斷系統調用,讓系統調用立刻返回,比如sleep,當進程正在sleep的時候,收到信號,sleep會馬上返回剩余sleep秒數,比如:

        <?php pcntl_signal(SIGINT,  function($signo) {      echo "Ctrl + Cn"; }, false);  while (true) { 	pcntl_signal_dispatch();     echo "123n";     $limit = sleep(2); 	echo "limit sleep [{$limit}] sn"; }

        運行之后,按Ctrl + C,結果如下所示:

        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(守護)進程

        這種進程一般設計為daemon進程,不受終端控制,不與終端交互,長時間運行在后臺,而對于一個進程,我們可以通過下面幾個步驟把他升級為一個標準的daemon進程:

        protected function daemonize() {     $pid = pcntl_fork();     if (-1 == $pid) {         throw new Exception("fork進程失敗");     } elseif ($pid != 0) {         exit(0);     }     if (-1 == posix_setsid()) {         throw new Exception("新建立session會話失敗");     }      $pid = pcntl_fork();     if (-1 == $pid) {         throw new Exception("fork進程失敗");     } else if($pid != 0) {         exit(0);     }      umask(0);     chdir("/"); }

        攏共分五步:

        1. fork子進程,父進程退出。
        2. 設置子進程為會話組長,進程組長。
        3. 再次fork,父進程退出,子進程繼續運行。
        4. 恢復文件掩碼為0
        5. 切換當前目錄到根目錄/。

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

        第3步是為了不讓進程重新控制終端,因為一個進程控制一個終端的必要條件是會話組長(pid=sid)。

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

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

        第5步是切換了當前目錄到根目錄/,網上說避免起始運行他的目錄不能被正確卸載,這個不是太了解。

        對應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

        另外,會話、進程組、進程的關系如下圖所示,這張圖有助于更好的理解。
        淺談PHP中的多進程消費隊列

        至此,你也可以輕松地造出一個daemon進程了。

        命令設計

        我準備給這個類庫設計6個命令,如下所示:

        1. start 啟動命令
        2. restart 強制重啟
        3. stop 平滑停止
        4. reload 平滑重啟
        5. quit 強制停止
        6. status 查看進程狀態

        啟動命令

        啟動命令就是默認的流程,按照默認流程走就是啟動命令,啟動命令會檢測pid文件中是否已經有pid,pid對應的進程是否健康,是否需要重新啟動。

        強制停止命令

        管理員通過入口文件結合pid給master進程發送SIGTERM信號,master進程給所有子進程發送SIGKILL信號,等待所有worker進程退出后,master進程也退出。

        強制重啟命令

        強制停止命令 + 啟動命令

        平滑停止命令

        平滑停止命令,管理員給master進程發送SIGINT信號,master進程給所有子進程發送SIGINT,worker進程將自身狀態標記為stoping,當worker進程下次循環的時候會根據stoping決定停止,不在接收新的數據,等所有worker進程退出之后,master進程也退出。

        平滑重啟命令

        平滑停止命令 + 啟動命令

        查看進程狀態

        查看進程狀態這個借鑒了workerman的思路,管理員給master進程發送SIGUSR1信號,告訴主進程,我要看所有進程的信息,master進程,master進程將自身的進程信息寫入配置好的文件路徑A中,然后發送SIGUSR1,告訴worker進程把自己的信息也寫入文件A中,由于這個過程是異步的,不知道worker進程啥時候寫完,所以master進程在此處等待,等所有worker進程都寫入文件之后,格式化所有的信息輸出,最后輸出的內容如下所示:

        ?/dir /usr/local/bin/php DaemonMcn.php status Daemon [DaemonMcn] 信息: -------------------------------- master進程狀態 -------------------------------- pid       占用內存       處理次數       開始時間                 運行時間 16343     0.75M          --             2018-05-15 09:42:45      0 天 0 時 3 分 12 slaver -------------------------------- slaver進程狀態 -------------------------------- 任務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 分 -------------------------------------------------------------------------------- 任務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進程將進程信息寫入文件的時候,這個地方用了個比較trick的方法,每個worker進程輸出一行信息,統計文件的行數,達到worker進程的行數之后表示所有worker進程都將信息寫入完畢,否則,每個1s檢測一次。

        其他設計

        另外還加了兩個比較實用的功能,一個是worker進程運行時間限制,一個是worker進程循環處理次數限制,防止長時間循環進程出現內存溢出等意外情況。時間默認是1小時,運行次數默認是10w次。

        除此之外,也可以支持多任務,每個任務幾個進程獨立開,統一由master進程管理。

        代碼已經放到github中,有興趣的可以試試,不支持windows哦,有什么錯誤還望指出來。

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 国产精品成人va| 国内精品视频在线观看| 99精品福利国产在线| 人妻少妇精品视频一区二区三区| 91久久福利国产成人精品| 精品无码一区在线观看| 日韩AV毛片精品久久久| 国产精品成人观看视频网站| 国产在线不卡午夜精品2021| 日韩精品无码免费一区二区三区| 精品成人一区二区三区四区| 日韩精品在线观看视频| 97久久综合精品久久久综合| 亚洲国产精品一区二区第一页| 久久97久久97精品免视看 | 久久亚洲精品无码播放| 四虎影视国产精品亚洲精品hd | 成人国产精品秘 果冻传媒在线| 91久久婷婷国产综合精品青草| 欧美精品一区二区蜜臀亚洲| 亚洲高清国产拍精品26U| 老司机午夜网站国内精品久久久久久久久 | 国产精品多p对白交换绿帽| 无码人妻精品一区二区三区在线| 亚洲av午夜精品一区二区三区 | 久久久免费精品re6| 亚洲精品午夜无码专区| 手机日韩精品视频在线看网站| 精品无码人妻一区二区三区不卡| 国产精品成人69XXX免费视频| 91精品国产福利在线观看麻豆| 2021国产成人精品久久| 久久夜色精品国产亚洲| 四虎影视国产精品永久在线| 91精品国产91久久| 国产精品亚洲美女久久久| 国产精品综合专区中文字幕免费播放| 国产精品青草久久久久福利99| 国产精品一区二区三区免费| 久久精品国产欧美日韩| 日本欧美国产精品第一页久久|