浅谈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 相关文章推荐
PHP_Flame(Version:Progress)的原代码
Oct 09 PHP
vs中通过剪切板循环来循环粘贴不同内容
Apr 30 PHP
基于php下载文件的详解
Jun 02 PHP
用PHP解决的一个栈的面试题
Jul 02 PHP
PHP基于php_imagick_st-Q8.dll实现JPG合成GIF图片的方法
Jul 11 PHP
php实现通用的从数据库表读取数据到数组的函数实例
Mar 21 PHP
PHP语法小结之基础和变量
Nov 22 PHP
PHP计算当前坐标3公里内4个角落的最大最小经纬度实例
Feb 26 PHP
php中使用GD库做验证码
Mar 31 PHP
php实现xml与json之间的相互转换功能实例
Jul 07 PHP
PHP解决中文乱码
Apr 28 PHP
PHP实现给定一列字符,生成指定长度的所有可能组合示例
Jun 22 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的控制语句
2006/10/09 PHP
PHP新手上路(十)
2006/10/09 PHP
Avengerls vs Newbee BO3 第一场2.18
2021/03/10 DOTA
juqery 学习之三 选择器 层级 基本
2010/11/25 Javascript
js网页侧边随页面滚动广告效果实现
2011/04/14 Javascript
JS关键字变色实现思路及代码
2013/02/21 Javascript
jQuery制作仿Mac Lion OS滚动条效果
2015/02/10 Javascript
javascript HTML5 Canvas实现圆盘抽奖功能
2016/04/11 Javascript
JS中取二维数组中最大值的方法汇总
2016/04/17 Javascript
Bootstrap Modal对话框如何在关闭时触发事件
2016/12/02 Javascript
javaScript中定义类或对象的五种方式总结
2016/12/04 Javascript
node.js发送邮件email的方法详解
2017/01/06 Javascript
JS复制对应id的内容到粘贴板(Ctrl+C效果)
2017/01/23 Javascript
js实现常见的工具条效果
2017/03/02 Javascript
vue.js 使用v-if v-else发现没有执行解决办法
2017/05/15 Javascript
Angularjs 双向绑定时字符串的转换成数字类型的问题
2017/06/12 Javascript
在Js页面通过POST传递参数跳转到新页面详解
2017/08/25 Javascript
使用ef6创建oracle数据库的实体模型遇到的问题及解决方案
2017/11/09 Javascript
vue几个常用跨域处理方式介绍
2018/02/07 Javascript
Vue中使用canvas方法总结
2019/02/12 Javascript
vue 组件中使用 transition 和 transition-group实现过渡动画
2019/07/09 Javascript
Weex开发之WEEX-EROS开发踩坑(小结)
2019/10/16 Javascript
[03:09]DOTA2亚洲邀请赛 LGD战队出场宣传片
2015/02/07 DOTA
[02:56]DOTA2上海特锦赛小组赛解说FreeAgain采访花絮
2016/02/27 DOTA
ubuntu安装sublime3并配置python3环境的方法
2018/03/15 Python
Python 通过截图匹配原图中的位置(opencv)实例
2019/08/27 Python
Python openpyxl模块实现excel读写操作
2020/06/30 Python
python db类用法说明
2020/07/07 Python
python 如何区分return和yield
2020/09/22 Python
详解Html5中video标签那些属性和方法
2019/07/01 HTML / CSS
Farfetch中文官网:奢侈品牌时尚购物平台
2020/03/15 全球购物
店长职务说明书
2014/02/04 职场文书
期末学生评语大全
2014/04/24 职场文书
设计顾问服务计划书
2014/05/04 职场文书
个人更名证明
2015/06/23 职场文书
教师节主题班会方案
2015/08/17 职场文书