深化了解 PHP 高性能结构 Workerman 看护进程原理
我们好,我是码农先森。
看护进程望文生义便是能够在后台一向运转的进程,不会强占用户的会话终端,脱离了终端的操控。信任朋友们对这东西都不生疏了吧?假如连这个概念都还不能了解的话,主张回炉重造多看看 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 前调用,而在第2次 Fork 前调用呢,这样的话就不必 Fork 两次了?原因是组长进程是不能创立会话的,进程组ID 61052
和进程ID 61052
相同「即当时进程则为组长进程」,所以需求子进程来创立新的会话,这一点需求特别注意一下。
<?php
function 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");
// 第2次 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");
}
// 第2次创立进程,在根据 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 中完成看护进程形式的比方,期望本次的内容能对你有所协助。
感谢我们阅览,个人观念仅供参考,欢迎在谈论区宣布不同观念。
欢迎重视、共享、点赞、保藏、在看,我是微信大众号「码农先森」作者。