目錄
nginx進程模型
進程設計
進程信號量設計
PHP安裝修信號量
信号量和系统调用
daemon(守护)进程
命令设计
启动命令
强制停止命令
强制重启命令
平滑停止命令
平滑重启命令
查看进程状态
其他设计
首頁 後端開發 php教程 淺談PHP中的多進程消費隊列

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

Mar 04, 2021 pm 05:59 PM
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作為高效能伺服器的中流砥柱,為成千上萬的企業和個人服務,他的進程模型比較經典,如下所示:

nginx 进程模型 图片来自网络

管理員透過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 + C\n";
});

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 + C\n";
}, false);
登入後複製

这时候给父进程发送SIGINT信号,pcntl_wait会马上返回-1,信号对应的事件也会触发。

所以第三个参数大概意思就是,是否重新注册此信号,如果为false只注册一次,触发之后就返回,pcntl_wait就能收到消息,如果为true,会重复注册,不会返回,pcntl_wait收不到消息。

信号量和系统调用

信号量会打断系统调用,让系统调用立刻返回,比如sleep,当进程正在sleep的时候,收到信号,sleep会马上返回剩余sleep秒数,比如:

<?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";
}
登入後複製

运行之后,按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哦,有什么错误还望指出来。

更多编程相关知识,请访问:编程入门!!

以上是淺談PHP中的多進程消費隊列的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 帶來了多項新功能、安全性改進和效能改進,同時棄用和刪除了大量功能。 本指南介紹如何在 Ubuntu、Debian 或其衍生版本上安裝 PHP 8.4 或升級到 PHP 8.4

討論 CakePHP 討論 CakePHP Sep 10, 2024 pm 05:28 PM

CakePHP 是 PHP 的開源框架。它旨在使應用程式的開發、部署和維護變得更加容易。 CakePHP 基於類似 MVC 的架構,功能強大且易於掌握。模型、視圖和控制器 gu

CakePHP 檔案上傳 CakePHP 檔案上傳 Sep 10, 2024 pm 05:27 PM

為了進行文件上傳,我們將使用表單助理。這是文件上傳的範例。

如何設定 Visual Studio Code (VS Code) 進行 PHP 開發 如何設定 Visual Studio Code (VS Code) 進行 PHP 開發 Dec 20, 2024 am 11:31 AM

Visual Studio Code,也稱為 VS Code,是一個免費的原始碼編輯器 - 或整合開發環境 (IDE) - 可用於所有主要作業系統。 VS Code 擁有大量針對多種程式語言的擴展,可以輕鬆編寫

CakePHP 快速指南 CakePHP 快速指南 Sep 10, 2024 pm 05:27 PM

CakePHP 是一個開源MVC 框架。它使應用程式的開發、部署和維護變得更加容易。 CakePHP 有許多函式庫可以減少大多數常見任務的過載。

您如何在PHP中解析和處理HTML/XML? 您如何在PHP中解析和處理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

php程序在字符串中計數元音 php程序在字符串中計數元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

See all articles