浅谈PHP进程管理


Posted in PHP onMarch 08, 2019

这篇文章是对之前一篇文章的补充和改进, 创建一个主(master)进程,主进程安装定时器,每隔5分钟检测一次队列长度,根据队列长度计算需要的worker进程,

然后创建或者杀掉子进程。这样做的好处是防止队列堆积,任务得不到及时处理。更新业务代码,只需要reload操作即可。

整个流程有以下知识点:

创建守护进程的步骤:

  1. 设置默认文件权限
  2. fork一个进程,父进程退出
  3. 调用setsid创建一个新的会话
  4. 将当前工作目录更改为根目录
  5. 关闭不再需要的文件描述符

使用信号实现定时器
上一篇定时器依赖于系统的定时任务,这次使用闹钟信号实现,php 5.3.0以下的版本依赖于ticks,5.3.0及以上版本可使用pcntl_signal_dispatch

信号:提供了一种异步事件处理的方法,在某个信号出现时,进程有以下三种方式对信号进行处理

  1. 忽略此信号
  2. 捕捉信号
  3. 执行系统默认动作,大多数信号的默认动作是终止该进程

常见信号
SIGKILL,SIGSTOP是两种不能被用户忽略和捕捉的信号

SIGINT(2):程序终止信号,通常是Ctrl-C)时发出,用于通知前台进程组终止进程

SIGQUIT(3):和SIGINT类似, 但由QUIT字符(通常是Ctrl+/)来控制. 进程收到该消息退出时会产生core文件

SIGKILL(9):立即终止进程,不可被忽略捕捉或阻塞

SIGUSR1(10):用户定义信号

SIGUSR2(12):留给用户使用

SIGALRM(14):闹钟信号

SIGTERM(15):终止进程,可被程序捕捉,使得进程可以执行完清理操作。

SIGSTOP(19):停止一个进程,该进程还未结束, 只是暂停执行

防止产生僵尸进程
所有的进程在退出的时候都会成为僵尸进程,这时候如果父进程还在运行,没有调用wait或者waitpid,则僵尸进程占用的资源不会被清理,如果父进程已终止,僵尸进程由init进程进行清理。

抽调业务代码,主要代码如下

其中要注意的一点,创建守护进程关闭输入输出,错误输出流的时候,如果代码后面有echo等输出字符,将出现致命错误,需要在php代码中重定向输出流到/dev/null。或者在终端启动进程的时候进行重定向

<?php
define('PROC_MAX', 10);
define('PROC_MIN', 5);
 
$cmd = $argv[1];
$aPid = [];
$pidFile = __DIR__ . '/pid.pid';
$pid = file_get_contents($pidFile);
 
switch($cmd){
 case 'start' :
  if(posix_kill($pid, 0)){
   echo "gamelog process is already exsits!\n";
   return false;
  }
  //设置默认文件权限
  umask(022);
  //fork
  $pid = pcntl_fork();
  if($pid < 0){
   exit('fork error!');
  }else if($pid > 0){
   exit;
  }
  //脱离当前终端
  posix_setsid();
  //将当前工作目录更改为根目录
  chdir('/');
  //关闭文件描述符
  fclose(STDIN);
  fclose(STDOUT);
  fclose(STDERR);
  //重定向输入输出
  global $STDOUT, $STDERR;
  $STDOUT = fopen('/dev/null', 'a');
  $STDERR = fopen('/dev/null', 'a');
   
  cli_set_process_title('gamelog:master');
  $pid = posix_getpid();
  file_put_contents($pidFile, $pid);
  //闹钟信号
  pcntl_signal(SIGALRM, function() use (&$aPid) {
   pcntl_alarm(300);
   $workerNum = mt_rand(1, 20);//此处检测你需要的进程数
   $daemonNum = count($aPid);
    
   ($workerNum > PROC_MAX) && ($workerNum = PROC_MAX);
   if($daemonNum < $workerNum){
    $procNum = $workerNum - $daemonNum;
    $procNum = max(PROC_MIN, $procNum);
    for($p = 1; $p <= $procNum; $p++){
     $pid = pcntl_fork();
     if ($pid < 0) {
      exit('fork error!');
     } else if ($pid == 0) {
      cli_set_process_title('gamelog:worker');
      while (true) {
       //do your work
       usleep(100);
      }
      exit();
     } else {
      $aPid[] = $pid;
     }
    }
   }else if($daemonNum > $workerNum){
    $wokerNum = max($wokerNum, PROC_MIN);
    $killNum = $daemonNum - $workerNum;
    foreach($aPid as $key=>$pid){
     if(posix_kill($pid, SIGKILL)){
      unset($aPid[$key]);
      if(--$killNum <= 0){
       break;
      }
     }
    }
   }
  }, false);
   
  pcntl_signal(SIGUSR1, function() use (&$aPid, $pid){
   foreach($aPid as $key=>$chpid){
    if(!posix_kill($chpid, SIGKILL)){
     echo "kill child $chpid faild\n";
    }
   }
   posix_kill($pid, SIGKILL);
  }, false);
   
  pcntl_signal(SIGUSR2, function() use (&$aPid, $pid){
   foreach($aPid as $key=>$chpid){
    if(!posix_kill($chpid, SIGKILL)){
     echo "kill child $chpid faild\n";
    }
   }
   if(!posix_kill($pid, SIGALRM)){
    echo "restart gamelog faild\n";
   }
  }, false);
   
  posix_kill($pid, SIGALRM);
  while (true) {
   pcntl_signal_dispatch();
   $pid = pcntl_wait($status, WUNTRACED);//不阻塞
  }
  break;
  
 case 'stop' :
  if(!posix_kill($pid, SIGUSR1)){
   exit('stop gamelog process error!');
  }
  break;
 case 'reload' :
  if(!posix_kill($pid, SIGUSR2)){
   exit('restop gamelog process error!');
  }
  break;
 default :
  echo "Useage php signal.php start|stop|reload\n";
}

以上所述是小编给大家介绍的PHP进程管理详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

PHP 相关文章推荐
xajax写的留言本
Nov 25 PHP
php session 预定义数组
Mar 16 PHP
php面向对象全攻略 (四)构造方法与析构方法
Sep 30 PHP
PHP XML操作的各种方法解析(比较详细)
Jun 17 PHP
PHP中怎样保持SESSION不过期 原理及方案介绍
Aug 08 PHP
PHP中的Memcache详解
Apr 05 PHP
php的ZipArchive类用法实例
Oct 20 PHP
PHP生成随机字符串(3种方法)
Sep 25 PHP
PHP使用DOM和simplexml读取xml文档的方法示例
Feb 08 PHP
使用PHPStorm+XDebug搭建单步调试环境
Nov 19 PHP
php-msf源码详解
Dec 25 PHP
php如何获取Http请求
Apr 30 PHP
PHP多进程通信-消息队列使用
Mar 08 #PHP
浅谈PHP匿名函数和闭包
Mar 08 #PHP
使用PHPUnit进行单元测试并生成代码覆盖率报告的方法
Mar 08 #PHP
ThinkPHP中图片按比例切割的代码实例
Mar 08 #PHP
PHP的微信支付接口使用方法讲解
Mar 08 #PHP
PHP实现会员账号单唯一登录的方法分析
Mar 07 #PHP
PHP模糊查询技术实例分析【附源码下载】
Mar 07 #PHP
You might like
php+ajax导入大数据时产生的问题处理
2014/06/11 PHP
WordPress开发中自定义菜单的相关PHP函数使用简介
2016/01/05 PHP
php 解析xml 的四种方法详细介绍
2016/10/26 PHP
iis6手工创建网站后无法运行php脚本的解决方法
2017/06/08 PHP
万能的php分页类
2017/07/06 PHP
php支付宝系列之电脑网站支付
2018/05/30 PHP
弹出广告特效(一个IP只弹出一次)的代码
2007/07/27 Javascript
默认让页面的第一个控件选中的javascript代码
2009/12/26 Javascript
从盛大通行证上摘下来的身份证验证js代码
2011/01/11 Javascript
AeroWindow 基于JQuery的弹出窗口插件
2011/06/27 Javascript
JS数学函数Exp使用说明
2012/08/09 Javascript
Bootstrap每天必学之标签与徽章
2015/11/27 Javascript
Linux下为Node.js程序配置MySQL或Oracle数据库的方法
2016/03/19 Javascript
解决拦截器对ajax请求的拦截实例详解
2016/12/21 Javascript
Bootstrap 轮播(Carousel)插件
2016/12/26 Javascript
详解Jquery 遍历数组之$().each方法与$.each()方法介绍
2017/01/09 Javascript
vue指令以及dom操作详解
2017/03/04 Javascript
NodeJS爬虫实例之糗事百科
2017/12/14 NodeJs
把vue-router和express项目部署到服务器的方法
2018/02/21 Javascript
JS实现在线ps功能详解
2019/07/31 Javascript
layer弹出层倒计时关闭的实现方法
2019/09/27 Javascript
使用Python进行新浪微博的mid和url互相转换实例(10进制和62进制互算)
2014/04/25 Python
5种Python单例模式的实现方式
2016/01/14 Python
Python基于回溯法子集树模板实现8皇后问题
2017/09/01 Python
利用Django-environ如何区分不同环境
2018/08/26 Python
python 使用递归的方式实现语义图片分割功能
2020/07/16 Python
详解Python 最短匹配模式
2020/07/29 Python
python 如何将office文件转换为PDF
2020/09/22 Python
法国女性内衣购物网站:Glamuse
2019/05/13 全球购物
九州传奇上机题
2014/07/10 面试题
工商管理毕业生推荐信
2013/12/24 职场文书
户籍证明的格式
2014/01/13 职场文书
护理学院专科毕业生求职信
2014/06/28 职场文书
个人批评与自我批评发言稿
2014/09/28 职场文书
幼儿园国培研修日志
2015/11/13 职场文书
遇事可以测出您的见识与格局
2019/09/16 职场文书