深入理解 PHP 高性能框架 Workerman 守護進程原理

来源:https://www.cnblogs.com/yxhblogs/p/18323660
-Advertisement-
Play Games

守護進程顧名思義就是能夠在後臺一直運行的進程,不會霸占用戶的會話終端,脫離了終端的控制。相信朋友們對這東西都不陌生了吧?如果連這個概念都還不能理解的話,建議回爐重造多看看 Linux 進程管理相關的基礎知識。 ...


大家好,我是碼農先森。

守護進程顧名思義就是能夠在後臺一直運行的進程,不會霸占用戶的會話終端,脫離了終端的控制。相信朋友們對這東西都不陌生了吧?如果連這個概念都還不能理解的話,建議回爐重造多看看 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 相同「即當前進程則為組長進程」,所以需要子進程來創建新的會話,這一點需要特別註意一下。

<?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");

// 第二次 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 中實現守護進程模式的例子,希望本次的內容能對你有所幫助。

感謝大家閱讀,個人觀點僅供參考,歡迎在評論區發表不同觀點。


歡迎關註、分享、點贊、收藏、在看,我是微信公眾號「碼農先森」作者。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在學習和開發Python的時候,第一步的工作就是先準備好開發環境,包括相關常用的插件,以及一些輔助工具,這樣我們在後續的開發工作中,才能做到事半功倍。下麵介紹一些Python 開發環境的準備以及一些常用類庫模塊的安裝和使用的經驗總結,供大家參考瞭解。 ...
  • 1. C/C++的編譯過程 1.1. 預處理 1.2. 編譯 1.3. 彙編 1.3.1. 彙編過程 1.3.2. 目標文件 1.4. 鏈接 2. 編譯過程示例 2.1. 源代碼 2.2. 逐步編譯程式 2.2.1. 編譯指令 2.2.2. 鏈接報錯問題 2.3. 單步編譯 3. gcc/g++與g ...
  • 刷題筆記8.5-8.9 刷題順序依照labuladong演算法小抄 兩數之和(8.5) 初始化數組: int[] num = new int<length>; int[] num = {1,2,3,4}; 其中數組名代表指針變數,故不可以直接將數組名a賦值給數組名b 錯誤的複製:int[] b = a ...
  • 正文 周五寫了一整天學海計劃,周六跟了一天的編曲教程,今天則是把第三章剩下的教程跟完。最後,迴旋曲寫是寫出來了,就是把自己聽笑了。寫得實在太爛了。 昨天晚上買了回來的機票。9 月 10 號一早。該說不說機票是真的貴啊…… 同時,我發現我過去的機票,執飛飛機是波音 737-800…… 我怎麼才發現。我 ...
  • 我們可以通過 ArcGIS Pro 腳本將 NetCDF 文件批量轉為柵格文件並將其各個波段分別導出為 tif 文件,本文將對該腳本代碼及其在 ArcGIS Pro 中的配置過程進行介紹。 ...
  • V哥在 JUnit 框架源碼學習時總結的13個非常值得學習的點,希望也可以幫助到你提升編碼的功力,歡迎關註威哥愛編程,一起學習框架源碼,提升編程技巧,我是 V哥,愛 編程,一輩子。 ...
  • CUDA常見編譯器配置問題一覽 關註TechLead,復旦博士,分享雲服務領域全維度開發技術。擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,復旦機器人智能實驗室成員,國家級大學生賽事評審專家,發表多篇SCI核心期刊學術論文,阿裡雲認證的資深架構師,上億營收AI產品研發負責人。 編譯器配 ...
  • xJavaFxTool —— 一個基於 JavaFx 搭建的實用小工具集合,包括文件複製、Cron表達式生成器、編碼轉換、加密解密等幾十種開發中常用的工具。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...