_
| |
_ __ __ _ _ __ _ _| |_ ___
| '_ \ / _` | '__| | | | __/ _ \
| | | | (_| | | | |_| | || (_) |
|_| |_|\__,_|_| \__,_|\__\___/ .TIGERB.cn
An object-oriented multi process manager for PHP
複製代碼
在咱們實際的業務場景中(PHP技術棧),咱們可能須要定時或者近乎實時的執行一些業務邏輯,簡單的咱們可使用unix系統自帶的crontab實現定時任務,可是對於一些實時性要求比較高的業務就不適用了,因此咱們就須要一個常駐內存的任務管理工具,爲了保證明時性,一方面咱們讓它一直執行任務(適當的睡眠,保證cpu不被100%佔用),另外一方面咱們實現多進程保證併發的執行任務。php
綜上所述,個人目標就是:實現基於php-cli模式實現的master-worker多進程管理工具。其次,「我有這樣一個目標,我是怎樣一步步去分析、規劃和實現的」,這是本文的宗旨。linux
備註:下文中,父進程統稱爲master,子進程統稱爲worker。git
咱們把這一個大目標拆成多個小目標去逐個實現,以下:github
PHP fork進程的方法 pcntl_fork
, 這個你們應該有所瞭解,若是不知道的簡單google/bing一下應該很容易找到這個函數。接着FTM, 咱們看看pcntl_fork
這個函數的使用方式大體以下:redis
$pid = pcntl_fork(); // pcntl_fork 的返回值是一個int值
// 若是$pid=-1 fork進程失敗
// 若是$pid=0 當前的上下文環境爲worker
// 若是$pid>0 當前的上下文環境爲master,這個pid就是fork的worker的pid
複製代碼
接着看代碼:bash
$pid = pcntl_fork();
switch ($pid) {
case -1:
// fatal error 致命錯誤 全部進程crash掉
break;
case 0:
// worker context
exit; // 這裏exit掉,避免worker繼續執行下面的代碼而形成一些問題
break;
default:
// master context
pcntl_wait($status); // pcntl_wait會阻塞,例如直到一個子進程exit
// 或者 pcntl_waitpid($pid, $status, WNOHANG); // WNOHANG:即便沒有子進程exit,也會當即返回
break;
}
複製代碼
咱們看到master有調用pcntl_wait
或者pcntl_waitpid
函數,爲何呢?首先咱們在這裏得提到兩個概念,以下:併發
因此,pcntl_wait
或者pcntl_waitpid
的目的就是防止worker成爲殭屍進程(zombie process)。less
除此以外咱們還須要把咱們的master掛起和worker掛起,我使用的的是while循環,而後usleep(200000)
防止CPU被100%佔用。函數
最後咱們經過下圖(1-1)來簡單的總結和描述這個多進程實現的過程:工具
上面實現了多進程和多進程的常駐內存,那master如何去管理worker呢?答案:多進程通訊。話很少說google/bing一下,如下我列舉幾種方式:
因此我選擇了「命名管道」的方式。我設計的通訊流程大體以下:
接着仍是逐個擊破,固然話很少說仍是google/bing一下。posix_mkfifo
建立命名管道、fopen
打開文件(管道以文件形式存在)、fread
讀取管道、fclose
關閉管道就呼嘯而出,哈哈,這樣咱們就能很容易的實現咱們上面的思路的了。接着說說我在這裏遇到的問題:fopen
阻塞了,致使業務代碼沒法循環執行,一想不對啊,日常fopen
普通文件不存在阻塞行爲,這時候二話不說FTM搜fopen
,crtl+f頁面搜「block」,重點來了:
fopen() will block if the file to be opened is a fifo. This is true whether it's opened in "r" or "w" mode. (See man 7 fifo: this is the correct, default behaviour; although Linux supports non-blocking fopen() of a fifo, PHP doesn't).
翻譯下,大概意思就是「當使用fopen的r或者w模式打開一個fifo的文件,就會一直阻塞;儘管linux支持非阻塞的打開fifo,可是php不支持。」,得不到解決方案,不支持,感受要放棄,一想這種場景應該不會不支持吧,再去看看posix_mkfifo
,結果喜出望外:
<?php
$fh=fopen($fifo, "r+"); // ensures at least one writer (us) so will be non-blocking
stream_set_blocking($fh, false); // prevent fread / fwrite blocking
?>
The "r+" allows fopen to return immediately regardless of external writer channel.
複製代碼
結論使用「r+」,同時咱們又知道了使用stream_set_blocking
防止緊接着的fread
阻塞。接着咱們用下圖(1-2)來簡單的總結和描述這個master-worker通訊的方式。
最後咱們須要解決的問題就是master怎麼接受來自client的信號,google/bing結論:
master接收信號 -> pcntl_signal註冊對應信號的handler方法 -> pcntl_signal_dispatch() 派發信號到handler
複製代碼
以下圖(1-3)所示,
接着咱們只要實現不一樣信號下master&worker的策略,例如worker的重啓等。這裏須要注意的就是,當master接受到重啓的信號後,worker不要當即exit,而是等到worker的業務邏輯執行完成了以後exit。具體的方式就是:
master接收reload信號 -> master把reload信號寫worker管道 -> worker讀取到reload信號 -> worker添加劇啓標誌位 -> worker執行完業務邏輯後且檢測到重啓的標誌位後exit
複製代碼
上面梳理完咱們的實現方式後,接着咱們就開始碼代碼了。碼代碼以前進行簡單的建模,以下:
進程管理類Manager
- attributes
+ master: master對象
+ workers: worker進程對象池
+ waitSignalProcessPool: 等待信號的worker池
+ startNum: 啓動進程數量
+ userPasswd: linux用戶密碼
+ pipeDir: 管道存放路徑
+ signalSupport: 支持的信號
+ hangupLoopMicrotime: 掛起間隔睡眠時間
- method
+ welcome: 歡迎於
+ configure: 初始化配置
+ fork: forkworker方法
+ execFork: 執行forkworker方法
+ defineSigHandler: 定義信號handler
+ registerSigHandler: 註冊信號handler
+ hangup: 掛起主進程
複製代碼
進程抽象類Process
- attributes
+ type: 進程類型 master/worker
+ pid: 進程ID
+ pipeName: 管道名稱
+ pipeMode: 管道模式
+ pipeDir: 管道存放路徑
+ pipeNamePrefix: 管道名稱前綴
+ pipePath: 管道生成路徑
+ readPipeType: 讀取管道數據的字節數
+ workerExitFlag: 進程退出標誌位
+ signal: 當前接受到的信號
+ hangupLoopMicrotime: 掛起間隔睡眠時間
- method
+ hangup: 掛起進程(抽象方法)
+ pipeMake: 建立管道
+ pipeWrite: 寫管道
+ pipeRead: 讀管道
+ clearPipe: 清理管道文件
+ stop: 進程exit
複製代碼
master實體類MasterProcess
- attributes
+
- method
+ hangup: 掛起進程
複製代碼
worker實體類MasterProcess
- attributes
+
- method
+ dispatchSig: 定義worker信號處理方式
複製代碼
最後咱們須要作的就是優雅的填充咱們的代碼了。
我的知識還有不少不足,若是有寫的不對的地方,但願你們及時指正。
THX~