深入理解 PHP 高性能框架 Workerman 守护进程原理

news/2024/9/21 3:26:57/文章来源:https://www.cnblogs.com/yxhblogs/p/18323660

大家好,我是码农先森。

守护进程顾名思义就是能够在后台一直运行的进程,不会霸占用户的会话终端,脱离了终端的控制。相信朋友们对这东西都不陌生了吧?如果连这个概念都还不能理解的话,建议回炉重造多看看 Linux 进程管理相关的基础知识。在我们日常的编程中常见有类似 php think ...php artisan ...php yii ... 等命令启动需要一直执行的任务,都会通过 nohup 挂载到后台保持长期运行的状态。同样在 Workerman 中也是使用类似 php index.php start 的命令来启动进程,但不同的是它不需要利用 nohup 便可以挂载到后台运行。那有些朋友就会好奇它是怎么实现的呢?为了解决朋友们的疑惑,我们今天就重点深入分析一下 Workerman 守护进程的实现原理。

我们先了解一些进程相关的知识:

  • 父进程:父进程是生成其他进程的进程。当一个进程创建了另一个进程时,创建者被称为父进程,而被创建的进程则成为子进程。父进程可以通过进程标识符(PID)来识别它所创建的子进程。
  • 子进程:子进程是由父进程创建的新进程。子进程继承了父进程的一些属性,例如环境变量、文件描述符等。子进程独立于父进程运行,它可以执行自己的代码,并且具有自己的资源和内存空间。
  • 进程组:进程组是一组相关联的进程的集合。每个进程组都有一个唯一的进程组ID(PGID),用于标识该进程组。进程组通常由一个父进程创建,并且包含了与父进程具有相同会话ID(SID)的所有子进程。
  • 会话:会话是一组关联进程的集合,通常由用户登录到系统开始,直至用户注销或关闭终端会话结束,一个会话中的进程共享相同的控制终端。每个会话都有一个唯一的会话ID(SID),用于标识该会话。会话通常包含一个或多个进程组,其中第一个进程组成为会话的主进程组。

这些概念俗称八股文,向来都不怎么好理解,那我们来看个例子。执行了命令 php index.php 便产生了进程 61052「该进程的父进程是 Bash 进程 8243,这里不用管它」,然后通过 Fork 创建了子进程 61053 且其父进程就是 61052,这两个进程拥有共同的进程组 61052 和会话 8243。调用 posix_setsid 函数,将会为子进程 61053 开启新的进程组 61053 和新的会话 61053,这里的会话可以理解为一个新的命令窗口终端。最后子进程 61053 通过 Fork 创建了子进程 61054,进程 61053 升级成了父进程,这里再次 Fork 的原因是要避免被终端控制进程所关联,这个进程 61052 是在终端的模式下创建的,自此进程 61054 就形成了守护进程。

[manongsen@root phpwork]$ php index.php
[parent] 进程ID: 61052, 父进程ID: 8243, 进程组ID: 61052, 会话ID: 8243 
[parent1] 进程ID: 61052, 父进程ID: 8243, 进程组ID: 61052, 会话ID: 8243 退出了该进程
[child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61052, 会话ID: 8243 
[child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 
[parent2] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 退出了该进程
[child2] 进程ID: 61054, 父进程ID: 61053, 进程组ID: 61053, 会话ID: 61053 保留了该进程[manongsen@root phpwork]$ ps aux | grep index.php
root             66064   0.0  0.0 408105040   1472 s080  S+   10:00下午   0:00.00 grep index.php
root             61054   0.0  0.0 438073488    280   ??  S    10:00下午   0:00.00 php index.php

上面举例的进程信息,正是这段代码运行所产生的。如果看了这段代码且细心的朋友,会发现为什么 posix_setsid 这个函数不放在第一次 Fork 前调用,而在第二次 Fork 前调用呢,这样的话就不用 Fork 两次了?原因是组长进程是不能创建会话的,进程组ID 61052 和进程ID 61052 相同「即当前进程则为组长进程」,所以需要子进程来创建新的会话,这一点需要特别注意一下。

<?phpfunction echoMsg($prefix, $suffix="") {// 进程ID$pid = getmypid(); // 进程组ID$pgid = posix_getpgid($pid);// 会话ID$sid = posix_getsid($pid); // 父进程ID$ppid = posix_getppid();echo "[{$prefix}] 进程ID: {$pid}, 父进程ID: {$ppid}, 进程组ID: {$pgid}, 会话ID: {$sid} {$suffix}" . PHP_EOL;
}// [parent] 进程ID: 61052, 父进程ID: 8243, 进程组ID: 61052, 会话ID: 8243
echoMsg("parent");// 第一次 Fork 进程  
$pid = pcntl_fork();
if ( $pid < 0 ) {exit('fork error');
} else if( $pid > 0 ) {// [parent1] 进程ID: 61052, 父进程ID: 8243, 进程组ID: 61052, 会话ID: 8243 退出了该进程echoMsg("parent1", "退出了该进程");exit;
}// 创建的 子进程ID 为 61053 但 进程组、会话 还是和父进程是同一个
// [child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61052, 会话ID: 8243 
echoMsg("child1");// 调用 posix_setsid 函数,会创建一个新的会话和进程组,并设置 进程组ID 和 会话ID 为该 进程ID
if (-1 === \posix_setsid()) {throw new Exception("Setsid fail");
}// 现在会发现 进程组ID 和 会话ID 都变成了 61053 在这里相当于启动了一个类似 Linux 终端下的会话窗口
// [child1] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 
echoMsg("child1");// 第二次 Fork 进程
// 这里需要二次 Fork 进程的原因是避免被终端控制进程所关联,这个进程 61052 是在终端的模式下创建的
// 需要脱离这个进程 61052 以确保守护进程的稳定
$pid = pcntl_fork();
if ( $pid  < 0 ){exit('fork error');
} else if( $pid > 0 ) {// [parent2] 进程ID: 61053, 父进程ID: 61052, 进程组ID: 61053, 会话ID: 61053 退出了该进程echoMsg("parent2", "退出了该进程");exit;
}// 到这里该进程已经脱离了终端进程的控制,形成了守护进程
// [child2] 进程ID: 61054, 父进程ID: 61053, 进程组ID: 61053, 会话ID: 61053 保留了该进程
echoMsg("child2", "保留了该进程");sleep(100);

有时间的朋友最好自行执行代码并分析一遍,会有不一样的收获。这里假装你已经实践过了,这下我们来看 Workerman 的 Worker.php 文件中 554 行的 runAll 方法中的 static::daemonize() 这个函数,实现的流程逻辑和上面的例子几乎一样。不过这里还使用了 umask 这个函数,其主要的作用是为该进程所创建的文件或目录赋予相应的权限,保证有权限操作文件或目录。

// workerman/Worker.php:554
/*** Run all worker instances.* 运行进程* @return void*/
public static function runAll()
{static::checkSapiEnv();static::init();static::parseCommand();static::lock();// 创建进程并形成守护进程static::daemonize();static::initWorkers();static::installSignal();static::saveMasterPid();static::lock(\LOCK_UN);static::displayUI();static::forkWorkers();static::resetStd();static::monitorWorkers();
}// workerman/Worker.php:1262
/*** Run as daemon mode.* 使用守护进程模式运行* @throws Exception*/
protected static function daemonize()
{// 判断是否已经是守护状态、以及当前系统是否是 Linux 环境if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) {return;}// 设置 umask 为 0 则当前进程创建的文件权限都为 777 拥有最高权限\umask(0);// 第一次创建进程$pid = \pcntl_fork();if (-1 === $pid) {// 创建进程失败throw new Exception('Fork fail');} elseif ($pid > 0) {// 主进程退出exit(0);}// 子进程继续执行...// 调用 posix_setsid 函数,可以让进程脱离父进程,转变为守护进程if (-1 === \posix_setsid()) {throw new Exception("Setsid fail");}// 第二次创建进程,在基于 System V 的系统中,通过再次 Fork 父进程退出// 保证形成的守护进程,不会成为会话首进程,不会拥有控制终端$pid = \pcntl_fork();if (-1 === $pid) {// 创建进程失败throw new Exception("Fork fail");} elseif (0 !== $pid) {// 主进程退出exit(0);}// 子进程继续执行...
}

守护进程也是 Workerman 中重要的一部分,它保障了 Workerman 进程的稳定性。不像我们通过 nohup 启动的命令,挂起到后台之后,有时还神不知鬼不觉的就挂了,朋友们或许都有这样的经历吧。当然在市面上也有一些开源的守护进程管理软件,比如 supervisor 等,其次还有人利用会话终端 screen、tmux 等工具来实现。其实守护进程的实现方式有多种多样,我们这里只是为了分析 Workerman 中守护进程的实现原理,而引出了在 PHP 中实现守护进程模式的例子,希望本次的内容能对你有所帮助。

感谢大家阅读,个人观点仅供参考,欢迎在评论区发表不同观点。


欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/781585.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IT基础书籍汇集_sum

希望STUDENT过软考,所以有些基础书籍还是需要看看 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------虽然书籍,标注“著”的书籍…

antd表单的<a-form-item>使用自定义label插槽

item的label类型可以使字符串或者自定义label插槽。 1.直接使用字符串类型是最常见的<a-form-model-item prop="job" label="岗位"><a-input v-model="job" placeholder="请输入岗位" /> </a-form-model-item> 2.自…

Nexpose v6.6.264 for Linux Windows - 漏洞扫描

Nexpose v6.6.264 for Linux & Windows - 漏洞扫描Nexpose v6.6.264 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, release Aug 07, 2024 请访问原文链接:https://sysin.org/blog/nexpose-6/,查看最新版。原创作品,转载请保留出处。 作者主页:…

二、Linux系统安装和基本使用

Linux系统安装和基本使用 这里我想记录自己在学习中遇到的有趣的、让自己觉得学到了的点。 Vim的使用 文章中举出的两个git power的例子非常有意思,我们来分析一下: 宏录制The first example is to generate the following file:1 2 3 ..... 98 99 100This file contains 100…

Java学习笔记2--JDK的安装和配置

一.进入oracle官网,下载jdk oracle官网:Oracle | Cloud Applications and Cloud Platform ps:不同的浏览器,可能进入oracle官网,会只显示部分内容,所以建议使用google Chrome浏览器在下载之前,首先需要去查看本机电脑的配置型号,如下图,右键---此电脑---选择点击属性,…

Markdown指定图片比例

编写Markdown文档时,有时候直接插入的文档,并没有按照预想的比例出现,这个时候可以手动调整其比例,具体参考: <img src="C:\Users\admin\Pictures\your_pic.png" alt="pic_name" style="zoom:30%" >

Blazor开发框架Known-V2.0.7

V2.0.7 Known是基于Blazor的企业级快速开发框架,低代码,跨平台,开箱即用,一处代码,多处运行。官网:http://known.pumantech.com Gitee: https://gitee.com/known/Known Github:https://github.com/known/Known概述基于C#和Blazor的快速开发框架,开箱即用,跨平台。 模…

当 Spring 循环依赖碰上 Aysnc,调试过程中出现 BeanCurrentlyInCreationException,有点意思

开心一刻 前两天有个女生加我,我同意了 第一天,她和我聊文学,聊理想,聊篮球,聊小猫小狗 第二天,她和我说要看我腹肌 吓我一跳,我反手就删除拉黑,我特喵一肚子的肥肉,哪来的腹肌!循环依赖 关于 Spring 的循环依赖,我已经写了 4 篇Spring 的循环依赖,源码详细分析 →…

RAG知识库之构建知识库图谱

前面几篇文章谈了多种针对RAG的优化如多表示索引(Multi-representation indexing)、Raptor等但其都是存储在向量库中的,这里将介绍一种新的存储模式,图数据库,适合存储数据高度相关的数据。其存储实体与实体间的关系,存储着丰富的关系类型数据,能给RAG知识库带来更精准的…

《花100块做个摸鱼小网站! 》第二篇—后端应用搭建和完成第一个爬虫

一、前言 大家好呀,我是summo,前面已经教会大家怎么去阿里云买服务器(链接在这,需要自取),以及怎么搭建JDK、Redis、MySQL这些环境或者数据库。从这篇文章开始就进入正式的编码阶段了,我们从后端开始,先把热搜数据获取到,然后再开始前端部分。 本来我想把后端应用搭建…

《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(3)-Wireshark在MacOS系统上安装部署

1.简介 上一篇中介绍和讲解、分享了Wireshark在Windows系统上安装部署,今天就介绍和讲解、分享Wireshark在MacOS系统上安装部署。Wireshark不仅是Windows系统网络协议分析软件也是一款mac网络协议分析软件,任何负责的网络分析人员都对这个软件情有独钟。如今,几乎没有哪种产…

下一代浏览器和移动自动化测试框架:WebdriverIO

1、介绍 今天给大家推荐一款基于Node.js编写且号称下一代浏览器和移动自动化测试框架:WebdriverIO 简单来讲:WebdriverIO 是一个开源的自动化测试框架,它允许测试人员使用 Node.js 编写自动化测试脚本,用于测试Web应用、移动应用和桌面应用程序。能够执行端到端(e2e)、单…