├── process.jpg ├── task ├── task1.php ├── task2.php ├── member_2.php ├── framework.php ├── modules │ └── member_1.php └── serviceConf.php ├── s.php ├── test.php ├── systemd └── processmanager.service ├── composer.lock ├── composer.json ├── src ├── Config.php ├── Utils.php ├── Jobs.php ├── XRedis.php ├── Console.php ├── Logs.1.php ├── Logs.php └── Process.php ├── globalConfig.php ├── bin ├── processmanager └── processmanager.php └── README.md /process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zl8762385/ProcessManager/HEAD/process.jpg -------------------------------------------------------------------------------- /task/task1.php: -------------------------------------------------------------------------------- 1 | on("WorkerStart", function ($pool, $workerId) { 7 | echo "Worker#{$workerId} is started\n"; 8 | echo time() . "11 \n"; 9 | }); 10 | 11 | $pool->on("WorkerStop", function ($pool, $workerId) { 12 | echo "Worker#{$workerId} is stopped\n"; 13 | }); 14 | 15 | $pool->start(); 16 | 17 | -------------------------------------------------------------------------------- /systemd/processmanager.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ProcessManager Server 3 | After=network.target 4 | After=syslog.target 5 | 6 | [Service] 7 | Type=forking 8 | PIDFile=/media/clever/8685937c-af42-4319-aa9b-bb123ccd18ba/data/www/clever/processmanager/log/master.pid 9 | ExecStart=/usr/bin/php7.0 /media/clever/processmanager/processmanager.php start >> /media/clever/processmanager/log/server.log 2>&1 10 | ExecStop=/bin/kill $MAINPID 11 | ExecReload=/bin/kill -USR1 $MAINPID 12 | Restart=always 13 | 14 | [Install] 15 | WantedBy=multi-user.target graphical.target 16 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "f9e145f19aab48098b3c3dff4ccf3b7a", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "php": ">=7.0", 17 | "ext-swoole": ">=1.8.9" 18 | }, 19 | "platform-dev": [] 20 | } 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clever/processmanager", 3 | "description": "基于swoole的cli多进程管理管理", 4 | "keywords": [ 5 | "swoole", 6 | "多进程", 7 | "ProcessManager" 8 | ], 9 | "homepage": "https://github.com/zl8762385/ProcessManager", 10 | "license": "MIT", 11 | "require": { 12 | "php": ">=7.0", 13 | "ext-swoole": ">=1.8.9" 14 | }, 15 | "authors": [ 16 | { 17 | "name": "xiaoliang", 18 | "email": "zl8762385@163.com" 19 | } 20 | ], 21 | "autoload": { 22 | "psr-4": { 23 | "Clever\\ProcessManager\\": "src" 24 | } 25 | }, 26 | "bin": [ 27 | "processmanager" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /task/serviceConf.php: -------------------------------------------------------------------------------- 1 | ':memberConfig', //服务标识 9 | 10 | // 资源相关 11 | 'redis' => [ 12 | 'host' => '127.0.0.1', 13 | 'port' => '6379', 14 | 'preKey'=> 'task1-', 15 | //'password'=>'', 16 | ], 17 | 18 | // exec任务相关,name的名字不能相同 19 | 'exec' => [ 20 | [ 21 | 'files' => 'modules/member_1',// 文件路径,相对于业务目录。如:task/modules/member_1.php 22 | 'max_request' => 0, // 限制进程最大请求数 0=不限制请求书 >0超出销毁 23 | 'memory_limit' => 50, // 单位:MB 最大内存限制,超出将自动销毁重新启动 24 | 'workNum' => 2 25 | ], 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | $value) { 29 | if (isset($nameList[$value[$chckKey]])) { 30 | return true; 31 | } 32 | $nameList[$value[$chckKey]]=$value[$chckKey]; 33 | } 34 | 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /globalConfig.php: -------------------------------------------------------------------------------- 1 | false, // 是否以daemon启动,如您打算用supervisor管理,可在这里改为false 14 | 15 | //日志 16 | 'logPath'=> GLOBAL_PATH . "/$workerName/log",//log目录 17 | 'logSaveFileApp' => 'application.log', //默认log存储名字 18 | 'logSaveFileWorker'=> 'workers.log', // 进程启动相关log存储名字 19 | 'pidPath' => GLOBAL_PATH . "/$workerName/log", 20 | 21 | // 进程相关 22 | 'sleepTime' => 40000, // 防止进程CPU使用过高单位:MS,这里是一个保护措施 23 | 24 | //业务相关 25 | 'workerDir' => GLOBAL_PATH . "/$workerName/", // task工作目录,用来存放业务代码 26 | 'workerLoadFileBefore' => [ 27 | 'framework.php' 28 | ], // 执行任务钱,需要加载的外部框架文件,与 workerDir关联,例:__DIR__ . "/task" . "/framework.php" 29 | ]; -------------------------------------------------------------------------------- /src/Utils.php: -------------------------------------------------------------------------------- 1 | getCode() . PHP_EOL; 29 | $error .= '错误信息:' . $exception->getMessage() . PHP_EOL; 30 | $error .= '错误堆栈:' . $exception->getTraceAsString() . PHP_EOL; 31 | 32 | $logger->log($error, 'error'); 33 | } 34 | 35 | /* 36 | * 获取内存使用情况 37 | * @return string 38 | * */ 39 | public static function getMemoryUsage() { 40 | // 类型是MB,获取时需要手动加上 41 | return round(memory_get_usage() / (1024 * 1024), 2); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bin/processmanager: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | &$v ) { 38 | if ( isset( $v['files'] ) && !empty( $v['files'] ) ) { 39 | $v['name'] = parseScriptToName( $v['files'] ); 40 | } 41 | } 42 | } 43 | 44 | 45 | // 静态配饰和业务配置合并 46 | $config = array_merge( $globalConfig, $config ); 47 | 48 | //print_r( $config ); 49 | //exit; 50 | $console = new \Clever\ProcessManager\Console($opt, $config); 51 | $console->run(); 52 | 53 | 54 | // 解析exec->files脚本名称 55 | function parseScriptToName( $files ) { 56 | if ( empty( $files ) ) { 57 | return ''; 58 | } 59 | $fileName = strstr( $files, '/' ); 60 | return substr( $fileName, 1, strlen($fileName) ); 61 | } -------------------------------------------------------------------------------- /bin/processmanager.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | &$v ) { 38 | if ( isset( $v['files'] ) && !empty( $v['files'] ) ) { 39 | $v['name'] = parseScriptToName( $v['files'] ); 40 | } 41 | } 42 | } 43 | 44 | 45 | // 静态配饰和业务配置合并 46 | $config = array_merge( $globalConfig, $config ); 47 | 48 | //print_r( $config ); 49 | //exit; 50 | $console = new \Clever\ProcessManager\Console($opt, $config); 51 | $console->run(); 52 | 53 | 54 | // 解析exec->files脚本名称 55 | function parseScriptToName( $files ) { 56 | if ( empty( $files ) ) { 57 | return ''; 58 | } 59 | $fileName = strstr( $files, '/' ); 60 | return substr( $fileName, 1, strlen($fileName) ); 61 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # processManager 2 | 3 | * Daemon方式可自由切换,变为进程组长后可使用supervisor进行管理 4 | * 基于swoole的脚本管理,用于多进程和守护进程管理; 5 | * 可轻松让普通脚本变守护进程和多进程执行; 6 | * 进程个数可配置,可以根据配置一次性执行多条命令; 7 | * 子进程异常退出时,主进程收到信号,自动拉起重新执行; 8 | * 支持子进程平滑退出,防止重启服务对业务造成影响; 9 | * 子进程重启次数max_request 到达请求次数自动销毁 10 | * 子进程内存控制memory_limit,某daemon下进程达到内存上限,自动销毁,拉起新进程 11 | * 配置文件可以按照模块进行分组操作,一个配置文件可起多个消费任务 12 | * 第三方业务框架扩展,可通过配置文件指定业务代码的加载方式,并可以在业务代码前加载钩子来执行第三方框架需要的函数、类包等 13 | * 对每个服务增加了CPU保护措施(防止开发人员不写sleep这样会导致CPU占用过高),在底层增加了一个微秒级别的usleep控制。 14 | 15 | 16 | 17 | ## 1. 场景 18 | 19 | * PHP脚本需要跑一个或多个脚本消费队列/计算等任务 20 | * 实现脚本退出后自动拉起,防止消费队列不工作,影响业务 21 | * 其实supervisor可以轻松做个事情,这个只是PHP的另一种实现,不需要换技术栈 22 | 23 | ## 2. 流程图 24 | ![流程图](process.jpg) 25 | 26 | 27 | ## 3. 安装 28 | * https://github.com/zl8762385/ProcessManager.git 29 | * composer install 30 | * 根据自己业务配置,修改task/serviceConf.php 31 | 32 | 33 | ## 4. 配置实例 34 | * 一次性执行多个命令 35 | ``` 36 | 'exec' => [ 37 | [ 38 | 'files' => 'modules/member_1',// 文件路径,相对于业务目录。如:task/modules/member_1.php 39 | 'max_request' => 0, // 限制进程最大请求数 0=不限制请求书 >0超出销毁 40 | 'memory_limit' => 50, // 单位:MB 最大内存限制,超出将自动销毁重新启动 41 | 'workNum' => 2 42 | ], 43 | ], 44 | ``` 45 | ## 5. 运行 46 | 47 | ### 5.1 启动 48 | * chmod -R u+r log/ 49 | * php processmanager -s start -c serviceConf.php 50 | ### 5.2 平滑停止服务,根据子进程执行时间等待所有服务停止 51 | * php processmanager -s stop -c serviceConf.php 52 | ### 5.3 强制停止服务[慎用] 53 | * php processmanager -s exit -c serviceConf.php 54 | ### 5.4 强制重启 55 | * php processmanager -s restart -c serviceConf.php 56 | ### 5.5 监控 57 | * ps -ef| grep 'process' 58 | 59 | ### 5.6 启动参数说明 60 | ``` 61 | NAME 62 | php processmanager - manage processmanager 63 | 64 | SYNOPSIS 65 | php processmanager -s command [options] -c config file path 66 | Manage processmanager daemons. 67 | 68 | 69 | WORKFLOWS 70 | 71 | 72 | help [command] 73 | Show this help, or workflow help for command. 74 | 75 | -s restart 76 | Stop, then start processmanager master and workers. 77 | 78 | -s start 79 | Start processmanager master and workers. 80 | -s start -c ./config 81 | Start processmanager with specail config file. 82 | 83 | 84 | -s stop 85 | Wait all running workers smooth exit, please check processmanager status for a while. 86 | 87 | -s exit 88 | Kill all running workers and master PIDs. 89 | 90 | ``` 91 | 92 | ## 9. 感谢 93 | 94 | * [swoole](http://www.swoole.com/) 95 | 96 | ## 10. QQ交流群:521822615 97 | 98 | ## 11. 如此框架帮您解决了问题,麻烦您右上角star点一下,感谢您的支持. 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/Jobs.php: -------------------------------------------------------------------------------- 1 | config = Config::getConfig(); 30 | $this->workerOne = $workerOne; 31 | 32 | $this->logger = new Logs(Config::getConfig()['logPath'] ?? '', $this->config['logSaveFileApp'] ?? ''); 33 | 34 | $this->taskFile = $this->workerOne['files'] ?? ''; 35 | $this->taskName = $this->workerOne['name'] ?? ''; 36 | 37 | if ( isset( $this->config['workerLoadFileBefore'] ) && !empty( $this->config['workerLoadFileBefore'] ) ) { 38 | $this->workerLoadFileBefore = $this->config['workerLoadFileBefore']; 39 | } 40 | 41 | 42 | } 43 | 44 | /* 45 | * 运行单个进程作业 46 | * @return call func 47 | * */ 48 | public function run () { 49 | 50 | // 加载文件 51 | $this->loadFiles(); 52 | 53 | try { 54 | // 执行run 55 | $taskJobs= new $this->taskName(); 56 | call_user_func_array( array( $taskJobs, "run"), [] ); 57 | } catch( \Throwable $e ) { 58 | Utils::catchError($this->logger, $e); 59 | } catch( \Exception $e ) { 60 | Utils::catchError($this->logger, $e); 61 | 62 | } 63 | 64 | } 65 | 66 | /* 67 | * 加载文件 68 | * @return include 69 | * */ 70 | private function loadFiles() { 71 | // load外部框架文件 72 | $this->loadFrameworkBefore(); 73 | 74 | $this->require_file($this->taskFile . ".php"); 75 | } 76 | 77 | /* 78 | * 加载任务前融合业务框架中的代码,如您需要在任务中执行您业务代码,请看这里 79 | * @return include 80 | * */ 81 | private function loadFrameworkBefore() { 82 | 83 | foreach( $this->workerLoadFileBefore as $k => $file ) { 84 | $this->require_file( $file ); 85 | } 86 | } 87 | 88 | /* 89 | * 调用文件 90 | * @param $file string 文件名称 91 | * @reutrn include 92 | * */ 93 | public function require_file( $file = '' ) { 94 | $files= $this->config['workerDir'] . "/" . $file; 95 | try { 96 | 97 | if ( file_exists( $files ) ) { 98 | require_once $files; 99 | } else { 100 | throw new \Exception("file is not exists"); 101 | } 102 | } catch( \Throwable $e ) { 103 | Utils::catchError($this->logger, $e); 104 | } catch( \Exception $e ) { 105 | Utils::catchError($this->logger, $e); 106 | } 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/XRedis.php: -------------------------------------------------------------------------------- 1 | config = $config; 28 | $this->connect(); 29 | } 30 | 31 | /** 32 | * 调用redis. 33 | * 34 | * @param $method 35 | * @param $arguments 36 | * 37 | * @return mixed 38 | */ 39 | public function __call($method, $arguments) 40 | { 41 | if (!$this->handler) { 42 | $this->connect(); 43 | } 44 | 45 | return call_user_func_array([$this->handler, $method], $arguments); 46 | } 47 | 48 | public function get($key, $serialize = false) 49 | { 50 | if (!$this->handler) { 51 | $this->connect(); 52 | } 53 | if ($serialize === false) { 54 | isset($this->config['serialize']) && $serialize = $this->config['serialize']; 55 | } 56 | 57 | return $serialize ? unserialize($this->handler->get($key)) : $this->handler->get($key); 58 | } 59 | 60 | public function set($key, $value, $timeout = 0, $serialize = false) 61 | { 62 | if (!$this->handler) { 63 | $this->connect(); 64 | } 65 | if ($serialize === false) { 66 | isset($this->config['serialize']) && $serialize = $this->config['serialize']; 67 | } 68 | $value = $serialize ? serialize($value) : $value; 69 | if ($timeout > 0) { 70 | return $this->handler->set($key, $value, $timeout); 71 | } 72 | 73 | return $this->handler->set($key, $value); 74 | } 75 | 76 | public function hget($key, $hash, $serialize = false) 77 | { 78 | if (!$this->handler) { 79 | $this->connect(); 80 | } 81 | if ($serialize === false) { 82 | isset($this->config['serialize']) && $serialize = $this->config['serialize']; 83 | } 84 | 85 | return $serialize ? unserialize($this->handler->hget($key, $hash)) : $this->handler->hget($key, $hash); 86 | } 87 | 88 | public function hset($key, $hash, $value, $serialize = false) 89 | { 90 | if (!$this->handler) { 91 | $this->connect(); 92 | } 93 | if ($serialize === false) { 94 | isset($this->config['serialize']) && $serialize = $this->config['serialize']; 95 | } 96 | $value = $serialize ? serialize($value) : $value; 97 | 98 | return $this->handler->hset($key, $hash, $value); 99 | } 100 | 101 | /** 102 | * 创建handler. 103 | * 104 | * @throws Exception 105 | */ 106 | private function connect() 107 | { 108 | $this->handler = new Redis(); 109 | if (isset($this->config['keep-alive']) && $this->config['keep-alive']) { 110 | $fd = $this->handler->pconnect($this->config['host'], $this->config['port'], 60); 111 | } else { 112 | $fd = $this->handler->connect($this->config['host'], $this->config['port']); 113 | } 114 | if (isset($this->config['password'])) { 115 | $this->handler->auth($this->config['password']); 116 | } 117 | if (!$fd) { 118 | throw new Exception("Unable to connect to redis host: {$this->config['host']},port: {$this->config['port']}"); 119 | } 120 | //统一key前缀 121 | if (isset($this->config['preKey']) && !empty($this->config['preKey'])) { 122 | $this->handler->setOption(Redis::OPT_PREFIX, $this->config['preKey']); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Console.php: -------------------------------------------------------------------------------- 1 | opt=$opt; 20 | if (empty($this->opt)) { 21 | $this->printHelpMessage(); 22 | exit(1); 23 | } 24 | Config::setConfig($config); 25 | $this->config = Config::getConfig(); 26 | $this->logger = new Logs(Config::getConfig()['logPath'] ?? '', $this->config['logSaveFileApp'] ?? ''); 27 | } 28 | 29 | public function run() 30 | { 31 | $this->runOpt(); 32 | } 33 | 34 | public function start() 35 | { 36 | //启动 37 | $process = new Process(); 38 | $process->start(); 39 | } 40 | 41 | /** 42 | * 给主进程发送信号: 43 | * SIGUSR1 自定义信号,让子进程平滑退出 44 | * SIGTERM 程序终止,让子进程强制退出. 45 | * 46 | * @param [type] $signal 47 | */ 48 | public function stop($signal=SIGUSR1) 49 | { 50 | $this->logger->log(($signal == SIGUSR1) ? 'smooth to exit...' : 'force to exit...'); 51 | 52 | if (isset($this->config['pidPath']) && !empty($this->config['pidPath'])) { 53 | $masterPidFile=$this->config['pidPath'] .'/'.$this->config['serviceMark'].'_master.pid'; 54 | } else { 55 | die('config pidPath must be set!'); 56 | } 57 | 58 | if (file_exists($masterPidFile)) { 59 | $ppid=file_get_contents($masterPidFile); 60 | if (empty($ppid)) { 61 | exit('service is not running' . PHP_EOL); 62 | } 63 | //给主进程发送信号 64 | if (@\Swoole\Process::kill($ppid, $signal)) { 65 | $this->logger->log('[pid: ' . $ppid . '] has been stopped success'); 66 | } else { 67 | $this->logger->log('[pid: ' . $ppid . '] has been stopped fail'); 68 | } 69 | //$this->getRedis()->set(Process::MASTER_KEY, Process::STATUS_WAIT); 70 | $this->saveMasterData([Process::MASTER_KEY=>Process::STATUS_WAIT]); 71 | } else { 72 | exit('service is not running' . PHP_EOL); 73 | } 74 | } 75 | 76 | public function restart() 77 | { 78 | $this->logger->log('restarting...'); 79 | $this->exit(); 80 | sleep(3); 81 | $this->start(); 82 | } 83 | 84 | public function exit() 85 | { 86 | $this->stop(SIGTERM); 87 | } 88 | 89 | public function runOpt() 90 | { 91 | switch ($this->opt) { 92 | case 'start': 93 | $this->start(); 94 | break; 95 | case 'stop': 96 | $this->stop(); 97 | break; 98 | case 'exit': 99 | $this->exit(); 100 | break; 101 | case 'restart': 102 | $this->restart(); 103 | break; 104 | case 'help': 105 | $this->printHelpMessage(); 106 | break; 107 | 108 | default: 109 | $this->printHelpMessage(); 110 | break; 111 | } 112 | } 113 | 114 | public function printHelpMessage() 115 | { 116 | $msg=<<<'EOF' 117 | NAME 118 | php multiprocess - manage multiprocess 119 | 120 | SYNOPSIS 121 | php multiprocess command [options] 122 | Manage multiprocess daemons. 123 | 124 | 125 | WORKFLOWS 126 | 127 | 128 | help [command] 129 | Show this help, or workflow help for command. 130 | 131 | 132 | -s restart 133 | Stop, then start multiprocess master and workers. 134 | 135 | -s start 136 | Start multiprocess master and workers. 137 | -s start -c=./config 138 | Start multiprocess with specail config file. 139 | 140 | -s stop 141 | Wait all running workers smooth exit, please check multiprocess status for a while. 142 | 143 | -s exit 144 | Kill all running workers and master PIDs. 145 | 146 | 147 | EOF; 148 | echo $msg; 149 | } 150 | 151 | 152 | /* 153 | * 保存master状态 154 | * */ 155 | private function saveMasterData($data=[]) { 156 | $mName = $this->config['serviceMark'] ?? '' ; 157 | $pidInfoFile =$this->config['pidPath'] . '/' . $mName .'_'. Process::PID_INFO_FILE; 158 | 159 | file_put_contents($pidInfoFile, serialize($data)); 160 | } 161 | 162 | 163 | private function getRedis() 164 | { 165 | if ($this->redis && $this->redis->ping()) { 166 | return $this->redis; 167 | } 168 | $this->redis = new XRedis($this->config['redis']); 169 | 170 | return $this->redis; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Logs.1.php: -------------------------------------------------------------------------------- 1 | logPath = $logPath; 39 | if ($logSaveFileApp) { 40 | $this->logSaveFileApp = $logSaveFileApp; 41 | } 42 | } 43 | 44 | /** 45 | * 获取日志实例. 46 | * 47 | * @$logPath 48 | * 49 | * @param mixed $logPath 50 | * @param mixed $logSaveFileApp 51 | */ 52 | public static function getLogger($logPath='', $logSaveFileApp='') 53 | { 54 | if (isset(self::$instance) && self::$instance !== null) { 55 | return self::$instance; 56 | } 57 | self::$instance=new self($logPath, $logSaveFileApp); 58 | 59 | return self::$instance; 60 | } 61 | 62 | /** 63 | * 格式化日志信息. 64 | * 65 | * @param mixed $message 66 | * @param mixed $level 67 | * @param mixed $category 68 | * @param mixed $time 69 | */ 70 | public function formatLogMessage($message, $level, $category, $time) 71 | { 72 | return @date('Y/m/d H:i:s', $time) . " [$level] [$category] $message\n"; 73 | } 74 | 75 | /** 76 | * 日志分类处理. 77 | * 78 | * @param mixed $message 79 | * @param mixed $level 80 | * @param mixed $category 81 | * @param mixed $flush 82 | */ 83 | public function log($message, $level = 'info', $category = '', $flush = true) 84 | { 85 | if (empty($category)) { 86 | $category=$this->logSaveFileApp; 87 | } 88 | $this->logs[$category][] = [$message, $level, $category, microtime(true)]; 89 | $this->logCount++; 90 | if ($this->logCount >= self::MAX_LOGS || true == $flush) { 91 | $this->flush($category); 92 | } 93 | } 94 | 95 | /** 96 | * 日志分类处理. 97 | */ 98 | public function processLogs() 99 | { 100 | $logsAll=[]; 101 | foreach ((array) $this->logs as $key => $logs) { 102 | $logsAll[$key] = ''; 103 | foreach ((array) $logs as $log) { 104 | $logsAll[$key] .= $this->formatLogMessage($log[0], $log[1], $log[2], $log[3]); 105 | } 106 | } 107 | 108 | return $logsAll; 109 | } 110 | 111 | /** 112 | * 写日志到文件. 113 | */ 114 | public function flush() 115 | { 116 | if ($this->logCount <= 0) { 117 | return false; 118 | } 119 | $logsAll = $this->processLogs(); 120 | $this->write($logsAll); 121 | $this->logs = []; 122 | $this->logCount = 0; 123 | } 124 | 125 | /** 126 | * [write 根据日志类型写到不同的日志文件]. 127 | * 128 | * @param $logsAll 129 | * 130 | * @throws \Exception 131 | */ 132 | public function write($logsAll) 133 | { 134 | if (empty($logsAll)) { 135 | return; 136 | } 137 | //$this->logPath = ROOT_PATH . 'src/runtime/'; 138 | if (!is_dir($this->logPath)) { 139 | self::mkdir($this->logPath, [], true); 140 | } 141 | foreach ($logsAll as $key => $value) { 142 | if (empty($key)) { 143 | continue; 144 | } 145 | $fileName = $this->logPath . '/' . $key; 146 | 147 | if (($fp = @fopen($fileName, 'a')) === false) { 148 | throw new \Exception("Unable to append to log file: {$fileName}"); 149 | } 150 | @flock($fp, LOCK_EX); 151 | 152 | if (@filesize($fileName) > $this->maxFileSize * 1024 * 1024) { 153 | $this->rotateFiles($fileName); 154 | } 155 | @fwrite($fp, $value); 156 | @flock($fp, LOCK_UN); 157 | @fclose($fp); 158 | } 159 | } 160 | 161 | /** 162 | * Rotates log files. 163 | * 164 | * @param mixed $file 165 | */ 166 | protected function rotateFiles($file) 167 | { 168 | for ($i = $this->maxLogFiles; $i >= 0; --$i) { 169 | // $i == 0 is the original log file 170 | $rotateFile = $file . (0 === $i ? '' : '.' . $i); 171 | //var_dump($rotateFile); 172 | if (is_file($rotateFile)) { 173 | // suppress errors because it's possible multiple processes enter into this section 174 | if ($i === $this->maxLogFiles) { 175 | @unlink($rotateFile); 176 | } else { 177 | if ($this->rotateByCopy) { 178 | @copy($rotateFile, $file . '.' . ($i + 1)); 179 | if ($fp = @fopen($rotateFile, 'a')) { 180 | @ftruncate($fp, 0); 181 | @fclose($fp); 182 | } 183 | } else { 184 | @rename($rotateFile, $file . '.' . ($i + 1)); 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * Shared environment safe version of mkdir. Supports recursive creation. 193 | * For avoidance of umask side-effects chmod is used. 194 | * 195 | * @param string $dst path to be created 196 | * @param array $options newDirMode element used, must contain access bitmask 197 | * @param bool $recursive whether to create directory structure recursive if parent dirs do not exist 198 | * 199 | * @return bool result of mkdir 200 | * 201 | * @see mkdir 202 | */ 203 | private static function mkdir($dst, array $options, $recursive) 204 | { 205 | $prevDir = dirname($dst); 206 | if ($recursive && !is_dir($dst) && !is_dir($prevDir)) { 207 | self::mkdir(dirname($dst), $options, true); 208 | } 209 | $mode = isset($options['newDirMode']) ? $options['newDirMode'] : 0777; 210 | $res = mkdir($dst, $mode); 211 | @chmod($dst, $mode); 212 | 213 | return $res; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/Logs.php: -------------------------------------------------------------------------------- 1 | logPath = $logPath; 42 | if ($logSaveFileApp) { 43 | $this->logSaveFileApp = $logSaveFileApp; 44 | } 45 | 46 | $logSystem && $this->logSystem = $logSystem; 47 | } 48 | 49 | /** 50 | * 获取日志实例. 51 | * 52 | * @$logPath 53 | * 54 | * @param mixed $logPath 55 | * @param mixed $logSaveFileApp 56 | * @param mixed $logSystem 57 | */ 58 | public static function getLogger($logPath='', $logSaveFileApp='', $logSystem = '') 59 | { 60 | if (isset(self::$instance) && null !== self::$instance) { 61 | return self::$instance; 62 | } 63 | self::$instance=new self($logPath, $logSaveFileApp, $logSystem); 64 | 65 | return self::$instance; 66 | } 67 | 68 | /** 69 | * 格式化日志信息. 70 | * 71 | * @param mixed $message 72 | * @param mixed $level 73 | * @param mixed $category 74 | * @param mixed $time 75 | */ 76 | public function formatLogMessage($message, $level, $category, $time) 77 | { 78 | $pid = getmypid(); 79 | 80 | return @date('Y/m/d H:i:s', $time) . " YCFLOG [$this->logSystem] [$level] [$category] [PID$pid] \n $message \n"; 81 | } 82 | 83 | /** 84 | * 日志分类处理. 85 | * 86 | * @param mixed $message 87 | * @param mixed $level 88 | * @param mixed $category 89 | * @param mixed $flush 90 | */ 91 | public function log($message, $level = 'info', $category = '', $flush = true) 92 | { 93 | if (empty($category)) { 94 | $category=$this->logSaveFileApp; 95 | } 96 | $this->logs[$category][] = [$message, $level, $category, microtime(true)]; 97 | ++$this->logCount; 98 | if ($this->logCount >= self::MAX_LOGS || true == $flush) { 99 | $this->flush($category); 100 | } 101 | } 102 | 103 | /** 104 | * 日志分类处理. 105 | */ 106 | public function processLogs() 107 | { 108 | $logsAll=[]; 109 | foreach ((array) $this->logs as $key => $logs) { 110 | $logsAll[$key] = ''; 111 | foreach ((array) $logs as $log) { 112 | $logsAll[$key] .= $this->formatLogMessage($log[0], $log[1], $log[2], $log[3]); 113 | } 114 | } 115 | 116 | return $logsAll; 117 | } 118 | 119 | /** 120 | * 写日志到文件. 121 | */ 122 | public function flush() 123 | { 124 | if ($this->logCount <= 0) { 125 | return false; 126 | } 127 | $logsAll = $this->processLogs(); 128 | $this->write($logsAll); 129 | $this->logs = []; 130 | $this->logCount = 0; 131 | } 132 | 133 | /** 134 | * [write 根据日志类型写到不同的日志文件]. 135 | * 136 | * @param $logsAll 137 | * 138 | * @throws \Exception 139 | */ 140 | public function write($logsAll) 141 | { 142 | if (empty($logsAll)) { 143 | return; 144 | } 145 | //$this->logPath = ROOT_PATH . 'src/runtime/'; 146 | if (!is_dir($this->logPath)) { 147 | self::mkdir($this->logPath, [], true); 148 | } 149 | foreach ($logsAll as $key => $value) { 150 | if (empty($key)) { 151 | continue; 152 | } 153 | //日志分类文件夹 154 | $keyCat = strtr($key, ['.log'=>'']); 155 | //日志文件名 156 | $key = strtr($key, ['.log'=>'']) . '-' . date('Ymd', time()) . '.log'; 157 | if (!is_dir($this->logPath . '/' . $keyCat)) { 158 | self::mkdir($this->logPath . '/' . $keyCat, [], true); 159 | } 160 | 161 | $fileName = $this->logPath . '/' . $keyCat . '/' . $key; 162 | 163 | if (false === ($fp = @fopen($fileName, 'a'))) { 164 | throw new \Exception("Unable to append to log file: {$fileName}"); 165 | } 166 | @flock($fp, LOCK_EX); 167 | 168 | if (@filesize($fileName) > $this->maxFileSize * 1024 * 1024) { 169 | $this->rotateFiles($fileName); 170 | } 171 | @fwrite($fp, $value); 172 | @flock($fp, LOCK_UN); 173 | @fclose($fp); 174 | } 175 | } 176 | 177 | /** 178 | * Rotates log files. 179 | * 180 | * @param mixed $file 181 | */ 182 | protected function rotateFiles($file) 183 | { 184 | for ($i = $this->maxLogFiles; $i >= 0; --$i) { 185 | // $i == 0 is the original log file 186 | $rotateFile = $file . (0 === $i ? '' : '.' . $i); 187 | //var_dump($rotateFile); 188 | if (is_file($rotateFile)) { 189 | // suppress errors because it's possible multiple processes enter into this section 190 | if ($i === $this->maxLogFiles) { 191 | @unlink($rotateFile); 192 | } else { 193 | if ($this->rotateByCopy) { 194 | @copy($rotateFile, $file . '.' . ($i + 1)); 195 | if ($fp = @fopen($rotateFile, 'a')) { 196 | @ftruncate($fp, 0); 197 | @fclose($fp); 198 | } 199 | } else { 200 | @rename($rotateFile, $file . '.' . ($i + 1)); 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | /** 208 | * Shared environment safe version of mkdir. Supports recursive creation. 209 | * For avoidance of umask side-effects chmod is used. 210 | * 211 | * @param string $dst path to be created 212 | * @param array $options newDirMode element used, must contain access bitmask 213 | * @param bool $recursive whether to create directory structure recursive if parent dirs do not exist 214 | * 215 | * @return bool result of mkdir 216 | * 217 | * @see mkdir 218 | */ 219 | private static function mkdir($dst, array $options, $recursive) 220 | { 221 | $prevDir = dirname($dst); 222 | if ($recursive && !is_dir($dst) && !is_dir($prevDir)) { 223 | self::mkdir(dirname($dst), $options, true); 224 | } 225 | $mode = isset($options['newDirMode']) ? $options['newDirMode'] : 0777; 226 | $res = mkdir($dst, $mode); 227 | @chmod($dst, $mode); 228 | 229 | return $res; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/Process.php: -------------------------------------------------------------------------------- 1 | config = Config::getConfig(); 41 | 42 | if (Config::hasRepeatingName($this->config['exec'], 'name')) { 43 | die('exec name has repeating name,fetal error!'); 44 | } 45 | $this->logger = new Logs(Config::getConfig()['logPath'] ?? '', $this->config['logSaveFileApp'] ?? ''); 46 | 47 | if (isset($this->config['pidPath']) && !empty($this->config['pidPath'])) { 48 | $mName = $this->config['serviceMark'] ?? '' ; 49 | Utils::mkdir($this->config['pidPath']); 50 | $this->pidFile =$this->config['pidPath'] . '/' . $mName .'_'. $this->pidFile; 51 | $this->pidInfoFile =$this->config['pidPath'] . '/' . $mName .'_'. self::PID_INFO_FILE; 52 | $this->workerStatusFile =$this->config['pidPath'] . '/' . $mName .'_'. $this->workerStatusFile; 53 | } else { 54 | die('config pidPath must be set!'); 55 | } 56 | if (isset($this->config['serviceMark']) && !empty($this->config['serviceMark'])) { 57 | $this->serviceMark = $this->config['serviceMark']; 58 | } 59 | if (isset($this->config['sleepTime']) && !empty($this->config['sleepTime'])) { 60 | $this->sleepTime = $this->config['sleepTime']; 61 | } 62 | if (isset($this->config['logSaveFileWorker']) && !empty($this->config['logSaveFileWorker'])) { 63 | $this->logSaveFileWorker = $this->config['logSaveFileWorker']; 64 | } 65 | 66 | /* 67 | * master.pid 文件记录 master 进程 pid, 方便之后进程管理 68 | * 请管理好此文件位置, 使用 systemd 管理进程时会用到此文件 69 | * 判断文件是否存在,并判断进程是否在运行 70 | */ 71 | 72 | if (file_exists($this->pidFile)) { 73 | $pid=$this->getMasterPid(); 74 | if ($pid && @\Swoole\Process::kill($pid, 0)) { 75 | die('已有进程运行中,请先结束或重启' . PHP_EOL); 76 | } 77 | } 78 | 79 | // 是否以daemon方式启动 80 | if ( isset( $this->config['isDaemon'] ) && $this->config['isDaemon'] === true ) { 81 | \Swoole\Process::daemon(); 82 | } 83 | $this->ppid = getmypid(); 84 | $this->saveMasterPid(); 85 | $this->setProcessName('process master ' . $this->ppid . $this->serviceMark); 86 | } 87 | 88 | public function start() 89 | { 90 | $this->saveMasterData([self::MASTER_KEY =>self::STATUS_START]); 91 | if (!isset($this->config['exec'])) { 92 | die('config exec must be not null!'); 93 | } 94 | $this->logger->log('process start pid: ' . $this->ppid, 'info', $this->logSaveFileWorker); 95 | 96 | $this->configWorkersByNameNum=[]; 97 | foreach ($this->config['exec'] as $key => $value) { 98 | 99 | $workOne['name'] = $value['name']; 100 | $workOne['files'] = $value['files']; 101 | $workOne['max_request'] = $value['max_request']; 102 | $workOne['memory_limit'] = $value['memory_limit']; 103 | //子进程带上通用识别文字,方便ps查询进程 104 | // $workOne['binArgs']=array_merge($value['binArgs'], [$this->serviceMark]); 105 | //开启多个子进程 106 | for ($i = 0; $i < $value['workNum']; $i++) { 107 | $this->createWorker($i, $workOne); 108 | } 109 | $this->configWorkersByNameNum[$value['name']] = $value['workNum']; 110 | } 111 | 112 | if (empty($this->timer)) { 113 | $this->registSignal(); 114 | $this->registTimer(); 115 | }//启动成功,修改状态 116 | 117 | $this->saveMasterData([self::MASTER_KEY=>self::STATUS_RUNNING]); 118 | } 119 | 120 | public function startByWorkerName($workName) 121 | { 122 | $this->saveWorkerStatus([self::WORKER_STATUS_KEY . $workName=>self::STATUS_START]); 123 | foreach ($this->config['exec'] as $key => $value) { 124 | if ($value['name'] != $workName) { 125 | continue; 126 | } 127 | 128 | $workOne['name'] = $value['name']; 129 | $workOne['files'] = $value['files']; 130 | $workOne['max_request'] = $value['max_request']; 131 | $workOne['memory_limit'] = $value['memory_limit']; 132 | //子进程带上通用识别文字,方便ps查询进程 133 | // $workOne['binArgs']=array_merge($value['binArgs'], [$this->serviceMark]); 134 | //开启多个子进程 135 | for ($i = 0; $i < $value['workNum']; $i++) { 136 | $this->createWorker($i, $workOne); 137 | } 138 | } 139 | 140 | $this->saveWorkerStatus([self::WORKER_STATUS_KEY . $workName=>self::STATUS_RUNNING]); 141 | } 142 | 143 | /* 144 | * 启动子进程 跑业务代码 145 | * @param $workNum int 进程数 146 | * @param $workOne [] 进程相关数据 147 | * */ 148 | public function createWorker($workNum, $workOne) 149 | { 150 | $reserveProcess = new \Swoole\Process(function ($worker) use ($workNum, $workOne) { 151 | $this->checkMpid($worker); 152 | //$beginTime=microtime(true); 153 | try { 154 | // 设置子进程名称 155 | $this->setProcessName( "process master:{$this->ppid}, child name:{$workOne['files']}" ); 156 | $job = new Jobs( $workOne ); 157 | 158 | $num = 0; 159 | do { 160 | //echo Utils::getMemoryUsage(). "MB 进程PID : $worker->pid 活着\n"; 161 | $job->run(); 162 | 163 | $this->status=$this->getMasterData(self::MASTER_KEY); 164 | $flag = ( self::STATUS_RUNNING == $this->status ) ? true : false ; 165 | 166 | // 计算进程最大请求数 167 | if ( self::STATUS_RUNNING == $this->status && !empty( $workOne['max_request'] ) && $num > $workOne['max_request'] ) { 168 | $flag = false; 169 | } 170 | 171 | // 计算进程内存使用限制 172 | if ( Utils::getMemoryUsage() > $workOne['memory_limit'] ) { 173 | $flag = false; 174 | } 175 | 176 | usleep( $this->sleepTime ); 177 | ++$num; 178 | } while ( $flag ); 179 | 180 | 181 | } catch (\Throwable $e) { 182 | Utils::catchError($this->logger, $e); 183 | } catch (\Exception $e) { 184 | Utils::catchError($this->logger, $e); 185 | } 186 | $this->logger->log('worker id: ' . $workNum . ' is done!!!', 'info', $this->logSaveFileWorker); 187 | $worker->exit(0); 188 | }); 189 | $pid = $reserveProcess->start(); 190 | $this->workers[$pid] = $reserveProcess; 191 | $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workOne['name'], $pid, 'add'); 192 | $this->workersByPidName[$pid] =$workOne['name']; 193 | $this->saveWorkerStatus([self::WORKER_STATUS_KEY . $workOne['name'] =>self::STATUS_RUNNING]); 194 | $this->logger->log('worker id: ' . $workNum . ' pid: ' . $pid . ' is start...', 'info', $this->logSaveFileWorker); 195 | } 196 | 197 | //注册信号 198 | public function registSignal() 199 | { 200 | \Swoole\Process::signal(SIGTERM, function ($signo) { 201 | $this->killWorkersAndExitMaster(); 202 | }); 203 | 204 | \Swoole\Process::signal(SIGKILL, function ($signo) { 205 | $this->killWorkersAndExitMaster(); 206 | }); 207 | 208 | \Swoole\Process::signal(SIGUSR1, function ($signo) { 209 | $this->waitWorkers(); 210 | }); 211 | 212 | \Swoole\Process::signal(SIGCHLD, function ($signo) { 213 | while (true) { 214 | $ret = \Swoole\Process::wait(false); 215 | if ($ret) { 216 | $pid = $ret['pid']; 217 | $childProcess = $this->workers[$pid]; 218 | $workName=$this->workersByPidName[$pid]; 219 | $this->status=$this->getMasterData(self::MASTER_KEY); 220 | //根据wokerName,获取其运行状态 221 | $workNameStatus=$this->getWorkerStatus(self::WORKER_STATUS_KEY . $workName); 222 | //主进程状态为start,running且子进程组不是recover状态才需要拉起子进程 223 | if ($workNameStatus != Process::STATUS_RECOVER && ($this->status == Process::STATUS_RUNNING || $this->status == Process::STATUS_START)) { 224 | try { 225 | $i=0; 226 | //重启有可能失败,最多尝试10次 227 | while ($i <= 10) { 228 | $newPid = $childProcess->start(); 229 | if ($newPid > 0) { 230 | break; 231 | } 232 | $this->logger->log($workName . '子进程重启失败,子进程尝试' . $i . '次重启', 'info', $this->logSaveFileWorker); 233 | 234 | $i++; 235 | } 236 | } catch (\Throwable $e) { 237 | Utils::catchError($this->logger, $e, 'error: woker restart fail...'); 238 | } catch (\Exception $e) { 239 | Utils::catchError($this->logger, $e, 'error: woker restart fail...'); 240 | } 241 | if ($newPid > 0) { 242 | $this->logger->log("Worker Restart, kill_signal={$ret['signal']} PID=" . $newPid, 'info', $this->logSaveFileWorker); 243 | $this->workers[$newPid] = $childProcess; 244 | $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName, $newPid, 'add'); 245 | $this->workersByPidName[$newPid] =$workName; 246 | $this->saveWorkerStatus([self::WORKER_STATUS_KEY . $workName=>Process::STATUS_RUNNING]); 247 | } else { 248 | $this->saveWorkerStatus([self::WORKER_STATUS_KEY . $workName=>Process::STATUS_RECOVER]); 249 | $this->logger->log($workName . '子进程重启失败,该组子进程进入recover状态', 'info', $this->logSaveFileWorker); 250 | } 251 | } 252 | $this->logger->log("Worker Exit, kill_signal={$ret['signal']} PID=" . $pid, 'info', $this->logSaveFileWorker); 253 | unset($this->workers[$pid], $this->workersByPidName[$pid]); 254 | $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName, $pid, 'del'); 255 | $this->logger->log('Worker count: ' . count($this->workers) . ' [' . $workName . '] ' . $this->configWorkersByNameNum[$workName], 'info', $this->logSaveFileWorker); 256 | //如果$this->workers为空,且主进程状态为wait,说明所有子进程安全退出,这个时候主进程退出 257 | if (empty($this->workers) && $this->status == Process::STATUS_WAIT) { 258 | $this->logger->log('主进程收到所有信号子进程的退出信号,子进程安全退出完成', 'info', $this->logSaveFileWorker); 259 | $this->exitMaster(); 260 | } 261 | } else { 262 | break; 263 | } 264 | } 265 | }); 266 | } 267 | 268 | public function registTimer() 269 | { 270 | $this->timer=\Swoole\Timer::tick($this->checkTickTimer, function ($timerId) { 271 | foreach ($this->configWorkersByNameNum as $workName => $value) { 272 | $this->status =$this->getMasterData(self::MASTER_KEY); 273 | $workNameStatus=$this->getWorkerStatus(self::WORKER_STATUS_KEY . $workName); 274 | $workNameMembers=$this->getWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName); 275 | $this->checkChildProcess($workName, $workNameMembers); 276 | $count=count($workNameMembers); 277 | if ($count <= 0) { 278 | $this->saveWorkerStatus([self::WORKER_STATUS_KEY . $workName=>Process::STATUS_START]); 279 | $this->startByWorkerName($workName); 280 | $this->logger->log('主进程 recover 子进程:' . $workName, 'info', $this->logSaveFileWorker); 281 | } 282 | $this->logger->log('主进程状态:' . $this->status . ' 数量:' . count($this->workers), 'info', $this->logSaveFileWorker); 283 | $this->logger->log('[' . $workName . ']子进程状态:' . $workNameStatus . ' 数量:' . $count . ' pids:' . serialize($workNameMembers), 'info', $this->logSaveFileWorker); 284 | } 285 | }); 286 | } 287 | 288 | //检查子进程是否还活着 289 | private function checkChildProcess($workName, $members) 290 | { 291 | foreach ($members as $key => $pid) { 292 | if ($pid) { 293 | if (!@\Swoole\Process::kill($pid, 0)) { 294 | unset($this->workers[$pid], $this->workersByPidName[$pid]); 295 | $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName, $pid, 'del'); 296 | $this->logger->log('子进程异常退出:' . $pid . ' name:' . $workName, 'error', $this->logSaveFileWorker); 297 | } else { 298 | $this->logger->log('子进程正常:' . $pid . ' name:' . $workName, 'info', $this->logSaveFileWorker); 299 | } 300 | } 301 | } 302 | } 303 | 304 | //平滑等待子进程退出之后,再退出主进程 305 | private function killWorkersAndExitMaster() 306 | { 307 | //修改主进程状态为stop 308 | $this->status =self::STATUS_STOP; 309 | $this->saveMasterData([self::MASTER_KEY=>self::STATUS_STOP]); 310 | 311 | if ($this->workers) { 312 | foreach ($this->workers as $pid => $worker) { 313 | //强制杀workers子进程 314 | if (\Swoole\Process::kill($pid) == true) { 315 | unset($this->workers[$pid]); 316 | $this->logger->log('子进程[' . $pid . ']收到强制退出信号,退出成功', 'info', $this->logSaveFileWorker); 317 | } else { 318 | $this->logger->log('子进程[' . $pid . ']收到强制退出信号,但退出失败', 'info', $this->logSaveFileWorker); 319 | } 320 | 321 | $this->logger->log('Worker count: ' . count($this->workers), 'info', $this->logSaveFileWorker); 322 | } 323 | } 324 | $this->exitMaster(); 325 | } 326 | 327 | //强制杀死子进程并退出主进程 328 | private function waitWorkers() 329 | { 330 | //修改主进程状态为wait 331 | 332 | $this->saveMasterData([self::MASTER_KEY=>self::STATUS_WAIT]); 333 | $this->status = self::STATUS_WAIT; 334 | foreach ($this->configWorkersByNameNum as $key => $value) { 335 | $workName =$key; 336 | $this->saveWorkerStatus([self::WORKER_STATUS_KEY . $workName=>self::STATUS_WAIT]); 337 | } 338 | } 339 | 340 | //退出主进程 341 | private function exitMaster() 342 | { 343 | @unlink($this->pidFile); 344 | //退出主程 删除掉其他info文件信息 by:xiaoliang 345 | 346 | @unlink($this->pidInfoFile); 347 | @unlink($this->workerStatusFile); 348 | $this->clearMasterData(); 349 | $this->logger->log('Time: ' . microtime(true) . '主进程' . $this->ppid . '退出', 'info', $this->logSaveFileWorker); 350 | sleep(1); 351 | exit(); 352 | } 353 | 354 | /** 355 | * 设置进程名. 356 | * 357 | * @param mixed $name 358 | */ 359 | private function setProcessName($name) 360 | { 361 | //mac os不支持进程重命名 362 | if (function_exists('swoole_set_process_name') && PHP_OS != 'Darwin') { 363 | swoole_set_process_name($name); 364 | } 365 | } 366 | 367 | //主进程如果不存在了,子进程退出 368 | private function checkMpid(&$worker) 369 | { 370 | if (!@\Swoole\Process::kill($this->ppid, 0)) { 371 | $worker->exit(); 372 | $this->logger->log("Master process exited, I [{$worker['pid']}] also quit"); 373 | } 374 | } 375 | 376 | private function saveMasterPid() 377 | { 378 | file_put_contents($this->pidFile, $this->ppid); 379 | } 380 | 381 | private function getMasterPid() 382 | { 383 | return file_get_contents($this->pidFile); 384 | } 385 | 386 | private function saveMasterData($data=[]) 387 | { 388 | 389 | file_put_contents($this->pidInfoFile, serialize($data)); 390 | } 391 | 392 | private function clearMasterData() 393 | { 394 | $this->redis = $this->getRedis(); 395 | 396 | $data=$this->configWorkersByNameNum; 397 | foreach ((array) $data as $key => $value) { 398 | $value && $this->redis->del(self::WORKER_STATUS_KEY . $key); 399 | $value && $this->redis->del(self::REDIS_WORKER_MEMBER_KEY . $key); 400 | $this->logger->log('主进程退出前删除woker redis key: ' . $key, 'info', $this->logSaveFileWorker); 401 | } 402 | //$this->redis->del(self::MASTER_KEY); 403 | 404 | $this->logger->log('主进程退出前删除master redis key: status', 'info', $this->logSaveFileWorker); 405 | } 406 | 407 | private function setWorkerList($key, $member, $opt='add') 408 | { 409 | $this->redis = $this->getRedis(); 410 | if ($opt == 'add') { 411 | return $this->redis->sAdd($key, $member); 412 | } elseif ($opt == 'del') { 413 | return $this->redis->sRemove($key, $member); 414 | } 415 | } 416 | 417 | private function getWorkerList($key) 418 | { 419 | $this->redis = $this->getRedis(); 420 | 421 | return $this->redis->sMembers($key); 422 | } 423 | 424 | /* 425 | * 获取主程状态 426 | * by:xiaoliang 427 | * */ 428 | private function getMasterData($key) 429 | { 430 | if ( !file_exists( $this->pidInfoFile ) ) { 431 | return null; 432 | } 433 | $data=unserialize(file_get_contents($this->pidInfoFile)); 434 | 435 | if ($key) { 436 | return $data[$key] ?? null; 437 | } 438 | 439 | return $data; 440 | } 441 | 442 | 443 | /* 444 | * 保存worker状态 445 | * @param $data [] 数组 446 | * @return put 序列化数据 447 | * */ 448 | private function saveWorkerStatus($data=[]) { 449 | 450 | $this->redis = $this->getRedis(); 451 | foreach ((array) $data as $key => $value) { 452 | $key && $this->redis->set($key, $value); 453 | } 454 | /* 455 | $mergeData = $data; 456 | // 检查序列化文件,保存所有任务状态 457 | if ( file_exists( $this->workerStatusFile ) ) { 458 | $mergeData = array_merge( 459 | $data, 460 | unserialize( file_get_contents($this->workerStatusFile) ) 461 | ); 462 | } 463 | 464 | file_put_contents($this->workerStatusFile, serialize($mergeData)); 465 | */ 466 | } 467 | 468 | /* 469 | * 获取 worker状态 470 | * @param $key string key 471 | * @return string 472 | * */ 473 | private function getWorkerStatus($key) { 474 | 475 | $this->redis = $this->getRedis(); 476 | if ($key) { 477 | return $this->redis->get($key); 478 | } 479 | /* 480 | $data=unserialize(file_get_contents($this->workerStatusFile)); 481 | 482 | if ($key) { 483 | return $data[$key] ?? null; 484 | } 485 | 486 | return $data; 487 | */ 488 | } 489 | 490 | 491 | 492 | private function getRedis() 493 | { 494 | if ($this->redis && $this->redis->ping()) { 495 | return $this->redis; 496 | } 497 | $this->redis = new XRedis($this->config['redis']); 498 | 499 | return $this->redis; 500 | } 501 | } 502 | --------------------------------------------------------------------------------