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作为高性能服务器的支柱,为博的柜以及私服务,他的过程模型比较经典,如下所示:

图片 1

管理员通过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的简化版。
图片 2

经过信号量设计

信号量是过程中通讯的一样栽方式,比较简单,单功能吗较弱,只能发送信号于进程,进程根据信号做出不同的拍卖。

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

此外,会话、进程组、进程的干而下图所出示,这张图有助于重新好的亮。
图片 3

至今,你吧足以轻松地奔出一个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哦,有啊错还向指出来。

参照文章

任由记忆想到的参考文章,查了众忘记了

  1. Linux多任务编程(七)—Linux守护进程及其基础实验
  2. workerman源码

相关文章