Full Code of kcloze/multiprocess for AI

master 3a882e6a8583 cached
19 files
49.5 KB
14.2k tokens
59 symbols
1 requests
Download .txt
Repository: kcloze/multiprocess
Branch: master
Commit: 3a882e6a8583
Files: 19
Total size: 49.5 KB

Directory structure:
gitextract_s9djh6fr/

├── .gitignore
├── .php_cs
├── README.en.md
├── README.md
├── composer.json
├── config.php
├── multiprocess
├── multiprocess.php
├── src/
│   ├── Config.php
│   ├── Console.php
│   ├── Logs.php
│   ├── Process.php
│   ├── Utils.php
│   └── XRedis.php
├── systemd/
│   └── multiprocess.service
└── test/
    ├── cli/
    │   ├── test.php
    │   ├── test2.php
    │   └── test3.py
    └── testConfig.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
vendor/*
.DS_Store*
log/*
composer.lock
src/master.pid


================================================
FILE: .php_cs
================================================
<?php
$header = <<<'EOF'
This file is part of PHP CS Fixer.
(c) kcloze <pei.greet@qq.com>
This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.
EOF;

return PhpCsFixer\Config::create()
    ->setRiskyAllowed(true)
    ->setRules([
        '@Symfony'                   => true,
        '@Symfony:risky'             => true,
        'array_syntax'               => ['syntax' => 'short'],
        'combine_consecutive_unsets' => true,
        // one should use PHPUnit methods to set up expected exception instead of annotations
        'general_phpdoc_annotation_remove'      => ['expectedException', 'expectedExceptionMessage', 'expectedExceptionMessageRegExp'],
        'header_comment'                        => ['header' => $header],
        'heredoc_to_nowdoc'                     => true,
        'no_extra_consecutive_blank_lines'      => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block'],
        'no_unreachable_default_argument_value' => true,
        'no_useless_else'                       => true,
        'no_useless_return'                     => true,
        'ordered_class_elements'                => true,
        'ordered_imports'                       => true,
        'php_unit_strict'                       => true,
        'phpdoc_add_missing_param_annotation'   => true,
        'phpdoc_order'                          => true,
        'psr4'                                  => true,
        'strict_comparison'                     => false,
        'strict_param'                          => true,
        'binary_operator_spaces'                => ['align_double_arrow' => true, 'align_equals' => true],
        'concat_space'                          => ['spacing' => 'one'],
        'no_empty_statement'                    => true,
        'simplified_null_return'                => true,
        'no_extra_consecutive_blank_lines'      => true,
        'pre_increment'                         => false
    ])
    ->setFinder(
        PhpCsFixer\Finder::create()
            ->exclude('vendor')
            ->in(__DIR__)
    )
    ->setUsingCache(false)
;

================================================
FILE: README.en.md
================================================
# multiprocess [[中文文档]](README.md)

* Based on swoole script management, for multi-process and daemon management
* Easy to make the common PHP script change daemon and multi-process execution
* The number of processes can be configured and multiple commands can be executed at once
* Automatic restart when the child process exits in an abnormal way
* When the main process exits in an abnormal way, the sub-process exits (smoothing out) after the work is done.
* Not limited programming language, PHP/Python/Java/Golang/C# and other scripts can be managed



## Scenario

* PHP requires running one or more cli script consumption queues (resident)
* The implementation of the script automatically pulls up after the exit, preventing the consumption queue from working, affecting the business
* In fact, the supervisor can easily do something, this is just another implementation of PHP, no need to change the technology stack

## Flow
![流程图](flow.png)


## Installation
* git clone https://github.com/kcloze/multiprocess.git
* composer install
* modify config.php based on your business configuration


## Configure the instance
* Execute multiple commands at once
```
    'logPath'   => __DIR__ . '/log',
    'exec'      => [
        [
            'name'      => 'kcloze-test-1',
            'bin'       => '/usr/bin/php',
            'binArgs'   => [__DIR__ . '/test/test.php', 'oop', '123'],
            'workNum'   => 3,
        ],
        [
            'name'      => 'kcloze-test-2',
            'bin'       => '/usr/bin/php',
            'binArgs'   => [__DIR__ . '/test/test2.php', 'oop', '456'],
            'workNum'   => 5,
        ],
        [
            'name'      => 'kcloze-test-3',
            'bin'       => '/usr/bin/python',
            'binArgs'   => [__DIR__ . '/test/test3.py', 'oop', '369'],
            'workNum'   => 2,
        ],
    ],

```
## Running

### 1.start
* chmod -R u+r log/
* php multiprocess.php start >> log/worker.log 2>&1
### 2.stop
* php multiprocess.php stop
### 3.exit
* php multiprocess.php exit
### 4.restart
* php multiprocess.php restart >> log/worker.log 2>&1
### 5.monitor
* ps -ef|grep 'multi-process'

### Command
```
NAME
      php multiprocess - manage multiprocess

SYNOPSIS
      php multiprocess -s command [options] -c config file path
          Manage multiprocess daemons.


WORKFLOWS


      help [command]
      Show this help, or workflow help for command.

      -s restart
      Stop, then start multiprocess master and workers.

      -s start 
      Start multiprocess master and workers.
      -s start -c ./config
      Start multiprocess with specail config file.



      -s stop
      Wait all running workers smooth exit, please check multiprocess status for a while.

      -s exit
      Kill all running workers and master PIDs.

```



## Monitor

![monitor img](monitor.png)

## Change log

#### 2017-11-30
* Refactor mercilessly v2 version 
* Add exit param,waiting for the child processes run over


## Thanks

* [swoole](http://www.swoole.com/)

## Contact

QQ group:141059677



================================================
FILE: README.md
================================================
# multiprocess


* [[readme in english]](README.en.md)
* 基于swoole的脚本管理,用于多进程和守护进程管理;
* 可轻松让普通脚本变守护进程和多进程执行;
* 进程个数可配置,可以根据配置一次性执行多条命令;
* 子进程异常退出时,主进程收到信号,自动拉起重新执行;
* 支持子进程平滑退出,防止重启服务对业务造成影响;
* 不限定编程语言,PHP/Python/Java/Golang/C#等脚本都可以管理

## 1. 场景

* PHP/python/js等脚本需要跑一个或多个脚本消费队列/计算等任务
* 实现脚本退出后自动拉起,防止消费队列不工作,影响业务
* 其实supervisor可以轻松做个事情,这个只是PHP的另一种实现,不需要换技术栈

## 2. 流程图
![流程图](flow.png)


## 3. 安装
* git clone https://github.com/kcloze/multiprocess.git
* composer install
* 根据自己业务配置,修改config.php


## 4. 配置实例
* 一次性执行多个命令
```
    'logPath'   => __DIR__ . '/log',
    'exec'      => [
        [
            'name'      => 'kcloze-test-1',
            'bin'       => '/usr/bin/php',
            'binArgs'   => [__DIR__ . '/test/test.php', 'oop', '123'],
            'workNum'   => 3,
        ],
        [
            'name'      => 'kcloze-test-2',
            'bin'       => '/usr/bin/php',
            'binArgs'   => [__DIR__ . '/test/test2.php', 'oop', '456'],
            'workNum'   => 5,
        ],
        [
            'name'      => 'kcloze-test-3',
            'bin'       => '/usr/bin/python',
            'binArgs'   => [__DIR__ . '/test/test3.py', 'oop', '369'],
            'workNum'   => 2,
        ],
    ],

```
## 5. 运行

### 5.1 启动
* chmod -R u+r log/
* php multiprocess.php start >> log/system.log 2>&1
### 5.2 平滑停止服务,根据子进程执行时间等待所有服务停止
* php multiprocess.php stop
### 5.3 强制停止服务[慎用]
* php multiprocess.php exit
### 5.4 强制重启
* php multiprocess.php restart >> log/system.log 2>&1
### 5.5 监控
* ps -ef|grep 'multi-process'

### 5.6 启动参数说明
```
NAME
      php multiprocess - manage multiprocess

SYNOPSIS
      php multiprocess -s command [options] -c config file path
          Manage multiprocess daemons.


WORKFLOWS


      help [command]
      Show this help, or workflow help for command.

      -s restart
      Stop, then start multiprocess master and workers.

      -s start 
      Start multiprocess master and workers.
      -s start -c ./config
      Start multiprocess with specail config file.


      -s stop
      Wait all running workers smooth exit, please check multiprocess status for a while.

      -s exit
      Kill all running workers and master PIDs.

```
## 6. 服务管理
### 启动和关闭服务,有两种方式:

#### 6.1 php脚本(主进程挂了之后,需要手动启动)
```
./multiprocess.php start|stop|exit|restart

```



#### 6.2 使用systemd管理(故障重启、开机自启动)
[更多systemd介绍](https://www.swoole.com/wiki/page/699.html)

```
1. 根据自己项目路径,修改 systemd/multiprocess.service
2. sudo cp -f systemd/multiprocess.service /etc/systemd/system/
3. sudo systemctl --system daemon-reload
4. 服务管理
#启动服务
sudo systemctl start multiprocess.service
#reload服务
sudo systemctl reload multiprocess.service
#关闭服务
sudo systemctl stop multiprocess.service
```


## 7. 系统状态

![监控图](monitor.png)

## 8. change log

#### 2017-11-30
* 彻底重构v2版本
* 增加exit启动参数,默认stop等待子进程平滑退出


## 9. 感谢

* [swoole](http://www.swoole.com/)

## 10. 联系

qq群:141059677



================================================
FILE: composer.json
================================================
{
    "name": "kcloze/multiprocess",
    "description": "基于swoole的cli多进程管理",
    "keywords": [
        "swoole",
        "多进程",
        "multi-process"
    ],
    "homepage": "https://github.com/kcloze/multiprocess",
    "license": "MIT",
    "require": {
        "php": ">=7.0",
        "ext-swoole": ">=1.8.9"
    },
    "authors": [
        {
            "name": "kcloze",
            "email": "pei.greet@qq.com"
        }
    ],
    "autoload": {
        "psr-4": {
            "Kcloze\\MultiProcess\\": "src"
        }
    },
    "bin": [
        "multiProcess"
    ]
}


================================================
FILE: config.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

return $config = [
    //log目录
    'logPath'          => __DIR__ . '/log',
    'logSaveFileApp'   => 'application.log', //默认log存储名字
    'logSaveFileWorker'=> 'workers.log', // 进程启动相关log存储名字
    'pidPath'          => __DIR__ . '/log',
    'processName'      => ':swooleMultiProcess', // 设置进程名, 方便管理, 默认值 swooleTopicQueue
    'sleepTime'        => 3000, // 子进程退出之后,自动拉起暂停毫秒数
    'redis'            => [
        'host'  => '192.168.10.129',
        'port'  => '6379',
        'preKey'=> 'SwooleMultiProcess-',
        //'password'=>'',
        'select' => 0, // 操作库(可选参数,默认0)
        'serialize' => true, // 是否序列化(可选参数,默认true)
    ],

    //exec任务相关,name的名字不能相同
    'exec'      => [
        [
            'name'             => 'kcloze-test-1',
            'bin'              => '/usr/local/php7/bin/php',
            'binArgs'          => ['/mnt/hgfs/www/saletool/think', 'testAmqp', '0'],
            'workNum'          => 1, // 外部程序进程数(固定)
            'queueNumCacheKey' => 'test_mq_queue', // 控制动态进程数队列长度缓存key,注意缓存数据为["total" => 10000,"update_time" => 15812345678](可选参数,不设置或为空时只有固定进程)
            'dynamicWorkNum'   => 2, // 外部程序动态进程数,总进程数=固定+动态(可选参数,设置参数queueNumCacheKey时为必填参数)
            
        ],
        /* [
            'name'      => 'kcloze-test-1',
            'bin'       => '/usr/local/bin/php',
            'binArgs'   => [__DIR__ . '/test/cli/test.php', 'oop', '123'],
            'workNum'   => 3,
        ], */
        /* [
            'name'      => 'kcloze-test-2',
            'bin'       => '/usr/local/bin/php',
            'binArgs'   => [__DIR__ . '/test/cli/test.php', 'oop', '123'],
            'workNum'   => 2,
        ], */
       /*  [
            'name'      => 'kcloze-test-3',
            'bin'       => '/usr/local/bin/php',
            'binArgs'   => [__DIR__ . '/test/cli/test2.php', 'oop', '456'],
            'workNum'   => 5,
        ], */
        // [
        //     'name'      => 'kcloze-test-3',
        //     'bin'       => '/usr/bin/python',
        //     'binArgs'   => [__DIR__ . '/test/cli/test3.py', 'oop', '369'],
        //     'workNum'   => 2,
        // ],
    ],
];


================================================
FILE: multiprocess
================================================
#!/usr/bin/env php
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

define('APP_PATH', __DIR__);
date_default_timezone_set('Asia/Shanghai');

require APP_PATH . '/vendor/autoload.php';

$param                  = getopt('s:c:');
$opt                    =$param['s'] ?? '';
$configFile             =$param['c'] ?? APP_PATH . '/config.php';
if ($configFile && file_exists($configFile)) {
    $config = require_once $configFile;
} else {
    die('config file can not find!');
}

$console = new Kcloze\MultiProcess\Console($opt, $config);
$console->run();


================================================
FILE: multiprocess.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

define('APP_PATH', __DIR__);
date_default_timezone_set('Asia/Shanghai');

require APP_PATH . '/vendor/autoload.php';

$param                  = getopt('s:c::');
$opt                    =$param['s'] ?? '';
$configFile             =$param['c'] ?? APP_PATH . '/config.php';
if ($configFile && file_exists($configFile)) {
    $config = require_once $configFile;
} else {
    die('config file can not find!');
}

$console = new Kcloze\MultiProcess\Console($opt, $config);
$console->run();


================================================
FILE: src/Config.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Kcloze\MultiProcess;

class Config
{
    private static $config=[];

    public static function setConfig($config)
    {
        self::$config=$config;
    }

    public static function getConfig()
    {
        return self::$config;
    }

    public static function hasRepeatingName($config=[], $chckKey='name')
    {
        $nameList=[];
        foreach ($config as $key => $value) {
            if (isset($nameList[$value[$chckKey]])) {
                return true;
            }
            $nameList[$value[$chckKey]]=$value[$chckKey];
        }

        return false;
    }
}


================================================
FILE: src/Console.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Kcloze\MultiProcess;

class Console
{
    public $logger    = null;
    private $config   = [];
    private $opt      = [];
    private $redis    = null;

    public function __construct($opt, $config)
    {
        $this->opt=$opt;
        if (empty($this->opt)) {
            $this->printHelpMessage();
            exit(1);
        }
        Config::setConfig($config);
        $this->config = Config::getConfig();
        $this->logger = new Logs(Config::getConfig()['logPath'] ?? '', $this->config['logSaveFileApp'] ?? '');
    }

    public function run()
    {
        $this->runOpt();
    }

    public function start()
    {
        //启动
        $process = new Process();
        $process->start();
    }

    /**
     * 给主进程发送信号:
     *  SIGUSR1 自定义信号,让子进程平滑退出
     *  SIGTERM 程序终止,让子进程强制退出.
     *
     * @param [type] $signal
     */
    public function stop($signal=SIGUSR1)
    {
        $this->logger->log(($signal == SIGUSR1) ? 'smooth to exit...' : 'force to exit...');

        if (isset($this->config['pidPath']) && !empty($this->config['pidPath'])) {
            $masterPidFile=$this->config['pidPath'] . '/master.pid';
        } else {
            die('config pidPath must be set!');
        }

        if (file_exists($masterPidFile)) {
            $ppid=file_get_contents($masterPidFile);
            if (empty($ppid)) {
                exit('service is not running' . PHP_EOL);
            }
            //给主进程发送信号
            if (@\Swoole\Process::kill($ppid, $signal)) {
                $this->logger->log('[pid: ' . $ppid . '] has been stopped success');
            } else {
                $this->logger->log('[pid: ' . $ppid . '] has been stopped fail');
            }
            $this->getRedis()->set(Process::REDIS_MASTER_KEY, Process::STATUS_WAIT);
        } else {
            exit('service is not running' . PHP_EOL);
        }
    }

    public function restart()
    {
        $this->logger->log('restarting...');
        $this->exit();
        sleep(3);
        $this->start();
    }

    public function exit()
    {
        $this->stop(SIGTERM);
    }

    public function runOpt()
    {
        switch ($this->opt) {
            case 'start':
                $this->start();
                break;
            case 'stop':
                $this->stop();
                break;
            case 'exit':
                $this->exit();
                break;
            case 'restart':
                $this->restart();
                break;
            case 'help':
                $this->printHelpMessage();
                break;

            default:
                $this->printHelpMessage();
                break;
        }
    }

    public function printHelpMessage()
    {
        $msg=<<<'EOF'
NAME
      php multiprocess - manage multiprocess

SYNOPSIS
      php multiprocess command [options]
          Manage multiprocess daemons.


WORKFLOWS


      help [command]
      Show this help, or workflow help for command.


      -s restart
      Stop, then start multiprocess master and workers.

      -s start 
      Start multiprocess master and workers.
      -s start -c=./config
      Start multiprocess with specail config file.

      -s stop
      Wait all running workers smooth exit, please check multiprocess status for a while.

      -s exit
      Kill all running workers and master PIDs.


EOF;
        echo $msg;
    }

    private function getRedis()
    {
        if ($this->redis && $this->redis->ping()) {
            return $this->redis;
        }
        $this->redis   = new XRedis($this->config['redis']);

        return $this->redis;
    }
}


================================================
FILE: src/Logs.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Kcloze\MultiProcess;

class Logs
{
    const LEVEL_TRACE          = 'trace';
    const LEVEL_WARNING        = 'warning';
    const LEVEL_ERROR          = 'error';
    const LEVEL_INFO           = 'info';
    const LEVEL_PROFILE        = 'profile';
    const MAX_LOGS             = 10000;

    public $rotateByCopy       = true;
    public $maxLogFiles        = 5;
    public $maxFileSize        = 100; // in MB

    private $logPath      = '';
    //单个类型log
    private $logs                 = [];
    private $logCount             = 0;
    //默认log文件存储名
    private $logSaveFileApp       = 'application.log';

    private static $instance=null;

    public function __construct($logPath, $logSaveFileApp='')
    {
        if (empty($logPath)) {
            die('config logPath must be set!' . PHP_EOL);
        }
        Utils::mkdir($logPath);
        $this->logPath = $logPath;
        if ($logSaveFileApp) {
            $this->logSaveFileApp = $logSaveFileApp;
        }
    }

    /**
     * 获取日志实例.
     *
     * @$logPath
     *
     * @param mixed $logPath
     * @param mixed $logSaveFileApp
     */
    public static function getLogger($logPath='', $logSaveFileApp='')
    {
        if (isset(self::$instance) && self::$instance !== null) {
            return self::$instance;
        }
        self::$instance=new self($logPath, $logSaveFileApp);

        return self::$instance;
    }

    /**
     * 格式化日志信息.
     *
     * @param mixed $message
     * @param mixed $level
     * @param mixed $category
     * @param mixed $time
     */
    public function formatLogMessage($message, $level, $category, $time)
    {
        return @date('Y/m/d H:i:s', $time) . " [$level] [$category] $message\n";
    }

    /**
     * 日志分类处理.
     *
     * @param mixed $message
     * @param mixed $level
     * @param mixed $category
     * @param mixed $flush
     */
    public function log($message, $level = 'info', $category = '', $flush = true)
    {
        if (empty($category)) {
            $category=$this->logSaveFileApp;
        }
        $this->logs[$category][]      = [$message, $level, $category, microtime(true)];
        $this->logCount++;
        if ($this->logCount >= self::MAX_LOGS || true == $flush) {
            $this->flush($category);
        }
    }

    /**
     * 日志分类处理.
     */
    public function processLogs()
    {
        $logsAll=[];
        foreach ((array) $this->logs as $key => $logs) {
            $logsAll[$key] = '';
            foreach ((array) $logs as $log) {
                $logsAll[$key] .= $this->formatLogMessage($log[0], $log[1], $log[2], $log[3]);
            }
        }

        return $logsAll;
    }

    /**
     * 写日志到文件.
     */
    public function flush()
    {
        if ($this->logCount <= 0) {
            return false;
        }
        $logsAll = $this->processLogs();
        $this->write($logsAll);
        $this->logs     = [];
        $this->logCount = 0;
    }

    /**
     * [write 根据日志类型写到不同的日志文件].
     *
     * @param $logsAll
     *
     * @throws \Exception
     */
    public function write($logsAll)
    {
        if (empty($logsAll)) {
            return;
        }
        //$this->logPath = ROOT_PATH . 'src/runtime/';
        if (!is_dir($this->logPath)) {
            self::mkdir($this->logPath, [], true);
        }
        foreach ($logsAll as $key => $value) {
            if (empty($key)) {
                continue;
            }
            $fileName = $this->logPath . '/' . $key;

            if (($fp = @fopen($fileName, 'a')) === false) {
                throw new \Exception("Unable to append to log file: {$fileName}");
            }
            @flock($fp, LOCK_EX);

            if (@filesize($fileName) > $this->maxFileSize * 1024 * 1024) {
                $this->rotateFiles($fileName);
            }
            @fwrite($fp, $value);
            @flock($fp, LOCK_UN);
            @fclose($fp);
        }
    }

    /**
     * Rotates log files.
     *
     * @param mixed $file
     */
    protected function rotateFiles($file)
    {
        for ($i = $this->maxLogFiles; $i >= 0; --$i) {
            // $i == 0 is the original log file
            $rotateFile = $file . ($i === 0 ? '' : '.' . $i);
            //var_dump($rotateFile);
            if (is_file($rotateFile)) {
                // suppress errors because it's possible multiple processes enter into this section
                if ($i === $this->maxLogFiles) {
                    @unlink($rotateFile);
                } else {
                    if ($this->rotateByCopy) {
                        @copy($rotateFile, $file . '.' . ($i + 1));
                        if ($fp = @fopen($rotateFile, 'a')) {
                            @ftruncate($fp, 0);
                            @fclose($fp);
                        }
                    } else {
                        @rename($rotateFile, $file . '.' . ($i + 1));
                    }
                }
            }
        }
    }

    /**
     * Shared environment safe version of mkdir. Supports recursive creation.
     * For avoidance of umask side-effects chmod is used.
     *
     * @param string $dst       path to be created
     * @param array  $options   newDirMode element used, must contain access bitmask
     * @param bool   $recursive whether to create directory structure recursive if parent dirs do not exist
     *
     * @return bool result of mkdir
     *
     * @see mkdir
     */
    private static function mkdir($dst, array $options, $recursive)
    {
        $prevDir = dirname($dst);
        if ($recursive && !is_dir($dst) && !is_dir($prevDir)) {
            self::mkdir(dirname($dst), $options, true);
        }
        $mode = isset($options['newDirMode']) ? $options['newDirMode'] : 0777;
        $res  = mkdir($dst, $mode);
        @chmod($dst, $mode);

        return $res;
    }
}


================================================
FILE: src/Process.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Kcloze\MultiProcess;

class Process
{
    const CHILD_PROCESS_CAN_RESTART        ='staticWorker'; //子进程可以重启,进程个数固定
    const CHILD_PROCESS_CAN_NOT_RESTART    ='dynamicWorker'; //子进程不可以重启,进程个数根据队列堵塞情况动态分配

    const STATUS_START                     ='start'; //主进程启动中状态
    const STATUS_RUNNING                   ='runnning'; //主进程正常running状态
    const STATUS_WAIT                      ='wait'; //主进程wait状态
    const STATUS_STOP                      ='stop'; //主进程stop状态
    const STATUS_RECOVER                   ='recover'; //主进程recover状态
    const REDIS_MASTER_KEY                 ='Status'; //Redis主进程状态key
    const REDIS_WORKER_STATUS_KEY          ='Status-'; //Redis 子进程状态key
    const REDIS_WORKER_MEMBER_KEY          ='Members-'; //主进程recover状态

    public $processName    = ':swooleMultiProcess'; // 进程重命名, 方便 shell 脚本管理
    private $workers;
    private $workersByPidName;
    private $ppid;
    private $configWorkersByNameNum;
    private $checkTickTimer       = 5000; //检查服务是否正常定时器,单位ms
    private $sleepTime            = 2000; //子进程退出之后,自动拉起暂停毫秒数
    private $config               = [];
    private $pidFile              = 'master.pid';
    private $status               =''; //主进程状态
    private $timer                =''; //定时器id
    private $redis                =null; //redis连接
    private $logSaveFileWorker    = 'workers.log';

    private $queueMaxNum          = 1000; //队列达到一定长度,增加子进程个数
    private $workersInfoList      = []; // 子进程队列
    private $dynamicWorkerNum     = []; //动态(不能重启)子进程计数,最大数为每个脚本配置dynamicWorkNum,它的个数是动态变化的

    public function __construct()
    {
        $this->config  =  Config::getConfig();

        if (Config::hasRepeatingName($this->config['exec'], 'name')) {
            die('exec name has repeating name,fetal error!');
        }
        $this->logger = new Logs(Config::getConfig()['logPath'] ?? '', $this->config['logSaveFileApp'] ?? '');

        if (isset($this->config['pidPath']) && !empty($this->config['pidPath'])) {
            Utils::mkdir($this->config['pidPath']);
            $this->pidFile    =$this->config['pidPath'] . '/' . $this->pidFile;
        } else {
            die('config pidPath must be set!');
        }
        if (isset($this->config['processName']) && !empty($this->config['processName'])) {
            $this->processName = $this->config['processName'];
        }
        if (isset($this->config['sleepTime']) && !empty($this->config['sleepTime'])) {
            $this->sleepTime = $this->config['sleepTime'];
        }
        if (isset($this->config['logSaveFileWorker']) && !empty($this->config['logSaveFileWorker'])) {
            $this->logSaveFileWorker = $this->config['logSaveFileWorker'];
        }

        /*
         * master.pid 文件记录 master 进程 pid, 方便之后进程管理
         * 请管理好此文件位置, 使用 systemd 管理进程时会用到此文件
         * 判断文件是否存在,并判断进程是否在运行
         */

        if (file_exists($this->pidFile)) {
            $pid=$this->getMasterPid();
            if ($pid && @\Swoole\Process::kill($pid, 0)) {
                die('已有进程运行中,请先结束或重启' . PHP_EOL);
            }
        }

        \Swoole\Process::daemon();
        $this->ppid    = getmypid();
        $this->saveMasterPid();
        $this->setProcessName('multiprocess master ' . $this->ppid . $this->processName);
    }

    /**
     * 启动主进程.
     */
    public function start()
    {
        $this->saveMasterData([self::REDIS_MASTER_KEY =>self::STATUS_START]);
        if (!isset($this->config['exec'])) {
            die('config exec must be not null!');
        }
        $this->logger->log('process start pid: ' . $this->ppid, 'info', $this->logSaveFileWorker);

        $this->configWorkersByNameNum=[];
        foreach ($this->config['exec'] as $key => $value) {
            if (!isset($value['bin']) || !isset($value['binArgs'])) {
                $this->logger->log('config bin/binArgs must be not null!', 'error', $this->logSaveFileWorker);
            }

            $workOne['bin']     = $value['bin'];
            $workOne['name']    = $value['name'];
            $workOne['binArgs'] = $value['binArgs'];
            //开启多个子进程
            for ($i = 0; $i < $value['workNum']; ++$i) {
                $this->reserveExec($i, $workOne, self::CHILD_PROCESS_CAN_RESTART);
            }
            $this->configWorkersByNameNum[$value['name']] = $value['workNum'];
        }

        if (empty($this->timer)) {
            $this->registSignal();
            $this->registTimer();
        }//启动成功,修改状态

        $this->saveMasterData([self::REDIS_MASTER_KEY=>self::STATUS_RUNNING]);
    }

    public function startByWorkerName($workName)
    {
        $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $workName=>self::STATUS_START]);
        foreach ($this->config['exec'] as $key => $value) {
            if ($value['name'] != $workName) {
                continue;
            }
            if (!isset($value['bin']) || !isset($value['binArgs'])) {
                $this->logger->log('config bin/binArgs must be not null!', 'error', $this->logSaveFileWorker);
            }

            $workOne['bin']     = $value['bin'];
            $workOne['name']    = $value['name'];
            $workOne['binArgs'] = $value['binArgs'];
            //开启多个子进程
            for ($i = 0; $i < $value['workNum']; ++$i) {
                $this->reserveExec($i, $workOne);
            }
        }

        $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $workName=>self::STATUS_RUNNING]);
    }

    /**
     * 启动子进程,跑业务代码
     *
     * @param int    $workNum
     * @param mixed  $workOne
     * @param string $workerType 是否会重启 canRestart|unRestart
     */
    public function reserveExec($workNum, $workOne, $workerType=self::CHILD_PROCESS_CAN_RESTART)
    {
        $reserveProcess = new \Swoole\Process(function ($worker) use ($workNum, $workOne) {
            usleep($this->sleepTime * 1000); // usleep单位是微妙,$this->sleepTime * 1000 转为毫秒
            $this->checkMpid($worker);
            try {
                $this->logger->log('Worker exec: ' . $workOne['bin'] . ' ' . implode(' ', $workOne['binArgs']), 'info', $this->logSaveFileWorker);
                //执行一个外部程序
                $worker->exec($workOne['bin'], $workOne['binArgs']);
            } catch (\Throwable $e) {
                Utils::catchError($this->logger, $e);
            } catch (\Exception $e) {
                Utils::catchError($this->logger, $e);
            }
            $this->logger->log('worker id: ' . $workNum . ' is done!!!', 'info', $this->logSaveFileWorker);
            $worker->exit(0);
        });
        $pid                                       = $reserveProcess->start();
        $this->workers[$pid]                       = $reserveProcess;
        $this->workersInfoList[$pid]['type']       = $workerType;
        $this->workersInfoList[$pid]['workOne']    = $workOne;
        $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workOne['name'], $pid, 'add');
        $this->workersByPidName[$pid]              = $workOne['name'];
        $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $workOne['name'] =>self::STATUS_RUNNING]);
        $this->logger->log('worker id: ' . $workNum . ' pid: ' . $pid . ' is start... ' . $workerType, 'info', $this->logSaveFileWorker);
    }

    /**
     * 注册信号.
     */
    public function registSignal()
    {
        \Swoole\Process::signal(SIGTERM, function ($signo) {
            $this->killWorkersAndExitMaster();
        });
        \Swoole\Process::signal(SIGKILL, function ($signo) {
            $this->killWorkersAndExitMaster();
        });
        \Swoole\Process::signal(SIGUSR1, function ($signo) {
            $this->waitWorkers();
        });
        \Swoole\Process::signal(SIGCHLD, function ($signo) {
            while (true) {
                // 捕获回收子进程异常
                try {
                    $ret = \Swoole\Process::wait(false);
                } catch (\Exception $e) {
                    $this->logger->log('signoError: ' . $signo . $e->getMessage(), 'error', Logs::LOG_SAVE_FILE_WORKER);
                }
                if ($ret) {
                    $pid           = $ret['pid'];
                    $childProcess = $this->workers[$pid];
                    $workName = $this->workersByPidName[$pid];
                    $workerType = $this->workersInfoList[$pid]['type'];
                    $this->status=$this->getMasterData(self::REDIS_MASTER_KEY);
                    //根据wokerName,获取其运行状态
                    $workNameStatus=$this->getMasterData(self::REDIS_WORKER_STATUS_KEY . $workName);
                    //子进程为可重启进程,主进程状态为start,running且子进程组不是recover状态才需要拉起子进程
                    if (self::CHILD_PROCESS_CAN_RESTART == $workerType && self::STATUS_RECOVER != $workNameStatus && (self::STATUS_RUNNING == $this->status || self::STATUS_START == $this->status)) {
                        try {
                            $i=0;
                            //重启有可能失败,最多尝试10次
                            while ($i <= 10) {
                                $newPid  = $childProcess->start();
                                if ($newPid > 0) {
                                    break;
                                }
                                $this->logger->log($workName . '子进程重启失败,子进程尝试' . $i . '次重启', 'info', $this->logSaveFileWorker);

                                ++$i;
                            }
                        } catch (\Throwable $e) {
                            Utils::catchError($this->logger, $e, 'error: woker restart fail...');
                        } catch (\Exception $e) {
                            Utils::catchError($this->logger, $e, 'error: woker restart fail...');
                        }
                        if ($newPid > 0) {
                            $this->logger->log("Worker Restart, kill_signal={$ret['signal']} PID=" . $newPid, 'info', $this->logSaveFileWorker);
                            $this->workers[$newPid] = $childProcess;
                            $this->workersInfoList[$newPid]['type']      = $workerType;
                            $this->workersInfoList[$newPid]['workOne']   = $this->workersInfoList[$pid]['workOne'];
                            $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName, $newPid, 'add');
                            $this->workersByPidName[$newPid]        = $workName;
                            $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $workName=>self::STATUS_RUNNING]);
                        } else {
                            $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $workName=>self::STATUS_RECOVER]);
                            $this->logger->log($workName . '子进程重启失败,该组子进程进入recover状态', 'info', $this->logSaveFileWorker);
                        }
                    }
                    // 动态子进程
                    if (self::CHILD_PROCESS_CAN_NOT_RESTART == $workerType) {
                        --$this->dynamicWorkerNum[$workName];
                    }
                    $this->logger->log("Worker Exit, kill_signal={$ret['signal']} PID=" . $pid, 'info', $this->logSaveFileWorker);
                    unset($this->workers[$pid], $this->workersByPidName[$pid], $this->workersInfoList[$pid]);
                    $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName, $pid, 'del');
                    $this->logger->log('Worker count: ' . \count($this->workers) . '  [' . $workName . ']  ' . $this->configWorkersByNameNum[$workName], 'info', $this->logSaveFileWorker);
                    //如果$this->workers为空,且主进程状态为wait,说明所有子进程安全退出,这个时候主进程退出
                    if (empty($this->workers) && self::STATUS_WAIT == $this->status) {
                        $this->logger->log('主进程收到所有信号子进程的退出信号,子进程安全退出完成', 'info', $this->logSaveFileWorker);
                        $this->exitMaster();
                    }
                } else {
                    break;
                }
            }
        });
    }

    /**
     * 注册定时器.
     */
    public function registTimer()
    {
        $this->timer=\Swoole\Timer::tick($this->checkTickTimer, function ($timerId) {
            $workNameStatus = '';
            foreach ($this->configWorkersByNameNum as $workName => $value) {
                $this->status  =$this->getMasterData(self::REDIS_MASTER_KEY);
                $workNameStatus=$this->getMasterData(self::REDIS_WORKER_STATUS_KEY . $workName);
                $workNameMembers=$this->getWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName);
                $this->checkChildProcess($workName, $workNameMembers);
                $count=\count($workNameMembers);
                if ($count <= 0) {
                    $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $workName=>self::STATUS_START]);
                    $this->startByWorkerName($workName);
                    $this->logger->log('主进程 recover 子进程:' . $workName, 'info', $this->logSaveFileWorker);
                }
                $this->logger->log('主进程状态:' . $this->status . ' 数量:' . \count($this->workers), 'info', $this->logSaveFileWorker);
                $this->logger->log('[' . $workName . ']子进程状态:' . $workNameStatus . ' 数量:' . $count . ' pids:' . serialize($workNameMembers), 'info', $this->logSaveFileWorker);
            }
            // 动态进程控制todo
            foreach ($this->config['exec'] as $key => $value) {
                if (!isset($value['dynamicWorkNum']) || $value['dynamicWorkNum'] < 1 || !isset($value['queueNumCacheKey']) || !$value['queueNumCacheKey']) {
                    continue;
                }
                if (!isset($value['bin']) || !isset($value['binArgs'])) {
                    $this->logger->log('config bin/binArgs must be not null!', 'error', $this->logSaveFileWorker);
                }
                // 获取队列的缓存数据,根据入队列数量控制动态进程
                $queueCacheData = $this->getCacheData($value['queueNumCacheKey']);
                if(!$queueCacheData || !isset($queueCacheData["total"]) || !isset($queueCacheData["update_time"])) {
                    continue;
                }
                
                $this->dynamicWorkerNum[$value['name']] = isset($this->dynamicWorkerNum[$value['name']]) ? $this->dynamicWorkerNum[$value['name']] : 0;
                if ($queueCacheData["total"] < $this->queueMaxNum || $this->dynamicWorkerNum[$value['name']] >= $value['dynamicWorkNum']) {
                    continue;
                }
                // 由缓存的total和update_time组成的key控制是否需要启动进程(只运行一次)
                $runOneTimeKey = "sw_process_".$value['name']."_".$queueCacheData["total"]."_".$queueCacheData["update_time"];
                $runOneTimeRes = $this->getCacheData($runOneTimeKey);
                if($runOneTimeRes) {
                    continue;
                }
                $workOne['bin']   = $value['bin'];
                $workOne['name']  = $value['name'];
                $workOne['binArgs']= $value['binArgs'];
                $canStartNum = $value['dynamicWorkNum'] - $this->dynamicWorkerNum[$value['name']];
                //开启多个子进程
                for ($i = 0; $i < $canStartNum; ++$i) {
                    $this->reserveExec($i, $workOne, self::CHILD_PROCESS_CAN_NOT_RESTART);
                    ++$this->dynamicWorkerNum[$value['name']];
                }

                $this->setCacheData($runOneTimeKey,1,7200);
            }
        });
    }

    //检查子进程是否还活着
    private function checkChildProcess($workName, $members)
    {
        foreach ($members as $key => $pid) {
            if ($pid) {
                if (!@\Swoole\Process::kill($pid, 0)) {
                    unset($this->workers[$pid], $this->workersByPidName[$pid]);
                    $this->setWorkerList(self::REDIS_WORKER_MEMBER_KEY . $workName, $pid, 'del');
                    $this->logger->log('子进程异常退出:' . $pid . ' name:' . $workName, 'error', $this->logSaveFileWorker);
                } else {
                    $this->logger->log('子进程正常:' . $pid . ' name:' . $workName, 'info', $this->logSaveFileWorker);
                }
            }
        }
    }

    //平滑等待子进程退出之后,再退出主进程
    private function killWorkersAndExitMaster()
    {
        //修改主进程状态为stop
        $this->status              =self::STATUS_STOP;
        $this->saveMasterData([self::REDIS_MASTER_KEY=>self::STATUS_STOP]);

        if ($this->workers) {
            foreach ($this->workers as $pid => $worker) {
                //强制杀workers子进程
                if (true == \Swoole\Process::kill($pid)) {
                    unset($this->workers[$pid]);
                    $this->logger->log('子进程[' . $pid . ']收到强制退出信号,退出成功', 'info', $this->logSaveFileWorker);
                } else {
                    $this->logger->log('子进程[' . $pid . ']收到强制退出信号,但退出失败', 'info', $this->logSaveFileWorker);
                }

                $this->logger->log('Worker count: ' . \count($this->workers), 'info', $this->logSaveFileWorker);
            }
        }
        $this->exitMaster();
    }

    //强制杀死子进程并退出主进程
    private function waitWorkers()
    {
        //修改主进程状态为wait

        $this->saveMasterData([self::REDIS_MASTER_KEY=>self::STATUS_WAIT]);
        $this->status = self::STATUS_WAIT;
        foreach ($this->configWorkersByNameNum as $key => $value) {
            $workName                  =$key;
            $this->saveMasterData([self::REDIS_WORKER_STATUS_KEY . $workName=>self::STATUS_WAIT]);
        }
    }

    //退出主进程
    private function exitMaster()
    {
        @unlink($this->pidFile);
        $this->clearMasterData();
        $this->logger->log('Time: ' . microtime(true) . '主进程' . $this->ppid . '退出', 'info', $this->logSaveFileWorker);
        sleep(1);
        exit();
    }

    /**
     * 设置进程名.
     *
     * @param mixed $name
     */
    private function setProcessName($name)
    {
        //mac os不支持进程重命名
        if (\function_exists('swoole_set_process_name') && PHP_OS != 'Darwin') {
            swoole_set_process_name($name);
        }
    }

    //主进程如果不存在了,子进程退出
    private function checkMpid(&$worker)
    {
        if (!@\Swoole\Process::kill($this->ppid, 0)) {
            $worker->exit();
            $this->logger->log("Master process exited, I [{$worker['pid']}] also quit");
        }
    }

    private function saveMasterPid()
    {
        file_put_contents($this->pidFile, $this->ppid);
    }

    private function getMasterPid()
    {
        return file_get_contents($this->pidFile);
    }

    private function saveMasterData($data=[])
    {
        $this->redis   = $this->getRedis();
        foreach ((array) $data as $key => $value) {
            $key && $this->redis->set($key, $value);
        }
    }

    private function clearMasterData()
    {
        $this->redis = $this->getRedis();

        $data=$this->configWorkersByNameNum;
        foreach ((array) $data as $key => $value) {
            $value && $this->redis->del(self::REDIS_WORKER_STATUS_KEY . $key);
            $value && $this->redis->del(self::REDIS_WORKER_MEMBER_KEY . $key);
            $this->logger->log('主进程退出前删除woker redis key: ' . $key, 'info', $this->logSaveFileWorker);
        }
        $this->redis->del(self::REDIS_MASTER_KEY);

        $this->logger->log('主进程退出前删除master redis key: status', 'info', $this->logSaveFileWorker);
    }

    private function setWorkerList($key, $member, $opt='add')
    {
        $this->redis = $this->getRedis();
        if ('add' == $opt) {
            return $this->redis->sAdd($key, $member);
        } elseif ('del' == $opt) {
            return $this->redis->sRemove($key, $member);
        }
    }

    /**
     * 获取子进程列表.
     *
     * @param string $key
     *
     * @return mixed
     */
    private function getWorkerList($key)
    {
        $this->redis = $this->getRedis();

        return $this->redis->sMembers($key);
    }

    /**
     * 获取主进程数据.
     *
     * @param string $key
     *
     * @return mixed
     */
    private function getMasterData($key)
    {
        $this->redis = $this->getRedis();
        if ($key) {
            return $this->redis->get($key);
        }
    }

    /**
     * 获取缓存数据.
     *
     * @param string $key
     *
     * @return mixed
     */
    private function getCacheData($key)
    {
        $this->redis = $this->getRedis();
        if ($key) {
            return $this->redis->get($key);
        }

        return false;
    }

    /**
     * 设置缓存数据.
     *
     * @param string $key
     *
     * @return mixed
     */
    private function setCacheData($key,$value,$timeout = 3600)
    {
        $this->redis = $this->getRedis();
        return $this->redis->set($key,$value,$timeout);
    }

    /**
     * 获取redis实例.
     */
    private function getRedis()
    {
        if ($this->redis && $this->redis->ping()) {
            return $this->redis;
        }
        $this->redis   = new XRedis($this->config['redis']);

        return $this->redis;
    }
}


================================================
FILE: src/Utils.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Kcloze\MultiProcess;

class Utils
{
    /**
     * 循环创建目录.
     *
     * @param mixed $path
     * @param mixed $recursive
     * @param mixed $mode
     */
    public static function mkdir($path, $mode=0777, $recursive=true)
    {
        if (!is_dir($path)) {
            mkdir($path, $mode, $recursive);
        }
    }

    public static function catchError($logger, $exception, $error='')
    {
        $error .= '错误类型:' . get_class($exception) . PHP_EOL;
        $error .= '错误代码:' . $exception->getCode() . PHP_EOL;
        $error .= '错误信息:' . $exception->getMessage() . PHP_EOL;
        $error .= '错误堆栈:' . $exception->getTraceAsString() . PHP_EOL;

        $logger->log($error, 'error');
    }
}


================================================
FILE: src/XRedis.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Kcloze\MultiProcess;

use Exception;
use Redis;

class XRedis
{
    /**
     * @var Redis
     */
    private $handler;
    private $config;

    /**
     * @param $config
     */
    public function __construct($config)
    {
        $this->config = $config;
        $this->connect();
    }

    /**
     * 调用redis.
     *
     * @param $method
     * @param $arguments
     *
     * @return mixed
     */
    public function __call($method, $arguments)
    {
        if (!$this->handler) {
            $this->connect();
        }

        return call_user_func_array([$this->handler, $method], $arguments);
    }

    public function get($key, $serialize = false)
    {
        if (!$this->handler) {
            $this->connect();
        }
        if ($serialize === false) {
            isset($this->config['serialize']) && $serialize = $this->config['serialize'];
        }

        return $serialize ? unserialize($this->handler->get($key)) : $this->handler->get($key);
    }

    public function set($key, $value, $timeout = 0, $serialize = false)
    {
        if (!$this->handler) {
            $this->connect();
        }
        if ($serialize === false) {
            isset($this->config['serialize']) && $serialize = $this->config['serialize'];
        }
        $value = $serialize ? serialize($value) : $value;
        if ($timeout > 0) {
            return $this->handler->set($key, $value, $timeout);
        }

        return $this->handler->set($key, $value);
    }

    public function hget($key, $hash, $serialize = false)
    {
        if (!$this->handler) {
            $this->connect();
        }
        if ($serialize === false) {
            isset($this->config['serialize']) && $serialize = $this->config['serialize'];
        }

        return $serialize ? unserialize($this->handler->hget($key, $hash)) : $this->handler->hget($key, $hash);
    }

    public function hset($key, $hash, $value, $serialize = false)
    {
        if (!$this->handler) {
            $this->connect();
        }
        if ($serialize === false) {
            isset($this->config['serialize']) && $serialize = $this->config['serialize'];
        }
        $value = $serialize ? serialize($value) : $value;

        return $this->handler->hset($key, $hash, $value);
    }

    /**
     * 创建handler.
     *
     * @throws Exception
     */
    private function connect()
    {
        $this->handler = new Redis();
        if (isset($this->config['keep-alive']) && $this->config['keep-alive']) {
            $fd = $this->handler->pconnect($this->config['host'], $this->config['port'], 60);
        } else {
            $fd = $this->handler->connect($this->config['host'], $this->config['port']);
        }
        if (isset($this->config['password'])) {
            $this->handler->auth($this->config['password']);
        }
        if (!$fd) {
            throw new Exception("Unable to connect to redis host: {$this->config['host']},port: {$this->config['port']}");
        }
        // 选择数据库0-15
        if (isset($this->config['select']) && 0 <= $this->config['select'] && $this->config['select'] <= 15) {
            $this->handler->select($this->config['select']);
        }
        //统一key前缀
        if (isset($this->config['preKey']) && !empty($this->config['preKey'])) {
            $this->handler->setOption(Redis::OPT_PREFIX, $this->config['preKey']);
        }
    }
}


================================================
FILE: systemd/multiprocess.service
================================================
[Unit]
Description=Multiprocess Server
After=network.target
After=syslog.target

[Service]
Type=forking
PIDFile=/media/kcloze/8685937c-af42-4319-aa9b-bb123ccd18ba/data/www/kcloze/multiprocess/log/master.pid
ExecStart=/usr/bin/php7.0 /media/kcloze/8685937c-af42-4319-aa9b-bb123ccd18ba/data/www/kcloze/multiprocess/multiprocess.php start >> /media/kcloze/8685937c-af42-4319-aa9b-bb123ccd18ba/data/www/kcloze/multiprocess/log/server.log 2>&1
ExecStop=/bin/kill $MAINPID
ExecReload=/bin/kill -USR1 $MAINPID
Restart=always

[Install]
WantedBy=multi-user.target graphical.target

================================================
FILE: test/cli/test.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

ini_set('date.timezone', 'Asia/Shanghai');

echo 'test1 time: ' . date('Y-m-d H:i:s');

sleep(15);

$i= mt_rand(1, 5);
var_dump($i);
// if ($i == 3) {
//     NotExit();
// }


================================================
FILE: test/cli/test2.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

ini_set('date.timezone', 'Asia/Shanghai');
sleep(10);
echo 'test2 time: ' . date('Y-m-d H:i:s');
// while (true) {
//     echo '123' . PHP_EOL;
//     sleep(1);
// }
// sleep(10);

// $i= mt_rand(1, 5);
// var_dump($i);
// if ($i == 3) {
//     NotExit();
// }


================================================
FILE: test/cli/test3.py
================================================
#coding=utf-8

import time
def main():
    print "Hello,Python!"
    time.sleep(5)
main()

================================================
FILE: test/testConfig.php
================================================
<?php

/*
 * This file is part of PHP CS Fixer.
 * (c) kcloze <pei.greet@qq.com>
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

define('APP_PATH', dirname(__DIR__));
date_default_timezone_set('Asia/Shanghai');

require APP_PATH . '/vendor/autoload.php';
$config = require_once APP_PATH . '/config.php';

var_dump($config);

use Kcloze\MultiProcess\Config;

$result=Config::hasRepeatingName($config['exec'], 'name');
var_dump($result);
Download .txt
gitextract_s9djh6fr/

├── .gitignore
├── .php_cs
├── README.en.md
├── README.md
├── composer.json
├── config.php
├── multiprocess
├── multiprocess.php
├── src/
│   ├── Config.php
│   ├── Console.php
│   ├── Logs.php
│   ├── Process.php
│   ├── Utils.php
│   └── XRedis.php
├── systemd/
│   └── multiprocess.service
└── test/
    ├── cli/
    │   ├── test.php
    │   ├── test2.php
    │   └── test3.py
    └── testConfig.php
Download .txt
SYMBOL INDEX (59 symbols across 7 files)

FILE: src/Config.php
  class Config (line 12) | class Config
    method setConfig (line 16) | public static function setConfig($config)
    method getConfig (line 21) | public static function getConfig()
    method hasRepeatingName (line 26) | public static function hasRepeatingName($config=[], $chckKey='name')

FILE: src/Console.php
  class Console (line 12) | class Console
    method __construct (line 19) | public function __construct($opt, $config)
    method run (line 31) | public function run()
    method start (line 36) | public function start()
    method stop (line 50) | public function stop($signal=SIGUSR1)
    method restart (line 77) | public function restart()
    method exit (line 85) | public function exit()
    method runOpt (line 90) | public function runOpt()
    method printHelpMessage (line 115) | public function printHelpMessage()
    method getRedis (line 152) | private function getRedis()

FILE: src/Logs.php
  class Logs (line 12) | class Logs
    method __construct (line 34) | public function __construct($logPath, $logSaveFileApp='')
    method getLogger (line 54) | public static function getLogger($logPath='', $logSaveFileApp='')
    method formatLogMessage (line 72) | public function formatLogMessage($message, $level, $category, $time)
    method log (line 85) | public function log($message, $level = 'info', $category = '', $flush ...
    method processLogs (line 100) | public function processLogs()
    method flush (line 116) | public function flush()
    method write (line 134) | public function write($logsAll)
    method rotateFiles (line 168) | protected function rotateFiles($file)
    method mkdir (line 205) | private static function mkdir($dst, array $options, $recursive)

FILE: src/Process.php
  class Process (line 12) | class Process
    method __construct (line 44) | public function __construct()
    method start (line 91) | public function start()
    method startByWorkerName (line 123) | public function startByWorkerName($workName)
    method reserveExec (line 153) | public function reserveExec($workNum, $workOne, $workerType=self::CHIL...
    method registSignal (line 183) | public function registSignal()
    method registTimer (line 265) | public function registTimer()
    method checkChildProcess (line 323) | private function checkChildProcess($workName, $members)
    method killWorkersAndExitMaster (line 339) | private function killWorkersAndExitMaster()
    method waitWorkers (line 362) | private function waitWorkers()
    method exitMaster (line 375) | private function exitMaster()
    method setProcessName (line 389) | private function setProcessName($name)
    method checkMpid (line 398) | private function checkMpid(&$worker)
    method saveMasterPid (line 406) | private function saveMasterPid()
    method getMasterPid (line 411) | private function getMasterPid()
    method saveMasterData (line 416) | private function saveMasterData($data=[])
    method clearMasterData (line 424) | private function clearMasterData()
    method setWorkerList (line 439) | private function setWorkerList($key, $member, $opt='add')
    method getWorkerList (line 456) | private function getWorkerList($key)
    method getMasterData (line 470) | private function getMasterData($key)
    method getCacheData (line 485) | private function getCacheData($key)
    method setCacheData (line 502) | private function setCacheData($key,$value,$timeout = 3600)
    method getRedis (line 511) | private function getRedis()

FILE: src/Utils.php
  class Utils (line 12) | class Utils
    method mkdir (line 21) | public static function mkdir($path, $mode=0777, $recursive=true)
    method catchError (line 28) | public static function catchError($logger, $exception, $error='')

FILE: src/XRedis.php
  class XRedis (line 15) | class XRedis
    method __construct (line 26) | public function __construct($config)
    method __call (line 40) | public function __call($method, $arguments)
    method get (line 49) | public function get($key, $serialize = false)
    method set (line 61) | public function set($key, $value, $timeout = 0, $serialize = false)
    method hget (line 77) | public function hget($key, $hash, $serialize = false)
    method hset (line 89) | public function hset($key, $hash, $value, $serialize = false)
    method connect (line 107) | private function connect()

FILE: test/cli/test3.py
  function main (line 4) | def main():
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (56K chars).
[
  {
    "path": ".gitignore",
    "chars": 55,
    "preview": "vendor/*\n.DS_Store*\nlog/*\ncomposer.lock\nsrc/master.pid\n"
  },
  {
    "path": ".php_cs",
    "chars": 2211,
    "preview": "<?php\n$header = <<<'EOF'\nThis file is part of PHP CS Fixer.\n(c) kcloze <pei.greet@qq.com>\nThis source file is subject to"
  },
  {
    "path": "README.en.md",
    "chars": 3062,
    "preview": "# multiprocess [[中文文档]](README.md)\n\n* Based on swoole script management, for multi-process and daemon management\n* Easy "
  },
  {
    "path": "README.md",
    "chars": 2897,
    "preview": "# multiprocess\n\n\n* [[readme in english]](README.en.md)\n* 基于swoole的脚本管理,用于多进程和守护进程管理;\n* 可轻松让普通脚本变守护进程和多进程执行;\n* 进程个数可配置,可以"
  },
  {
    "path": "composer.json",
    "chars": 575,
    "preview": "{\n    \"name\": \"kcloze/multiprocess\",\n    \"description\": \"基于swoole的cli多进程管理\",\n    \"keywords\": [\n        \"swoole\",\n       "
  },
  {
    "path": "config.php",
    "chars": 2321,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "multiprocess",
    "chars": 700,
    "preview": "#!/usr/bin/env php\n<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file "
  },
  {
    "path": "multiprocess.php",
    "chars": 682,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "src/Config.php",
    "chars": 792,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "src/Console.php",
    "chars": 3829,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "src/Logs.php",
    "chars": 6048,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "src/Process.php",
    "chars": 21016,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "src/Utils.php",
    "chars": 912,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "src/XRedis.php",
    "chars": 3595,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "systemd/multiprocess.service",
    "chars": 572,
    "preview": "[Unit]\nDescription=Multiprocess Server\nAfter=network.target\nAfter=syslog.target\n\n[Service]\nType=forking\nPIDFile=/media/k"
  },
  {
    "path": "test/cli/test.php",
    "chars": 372,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "test/cli/test2.php",
    "chars": 459,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  },
  {
    "path": "test/cli/test3.py",
    "chars": 89,
    "preview": "#coding=utf-8\n\nimport time\ndef main():\n    print \"Hello,Python!\"\n    time.sleep(5)\nmain()"
  },
  {
    "path": "test/testConfig.php",
    "chars": 505,
    "preview": "<?php\n\n/*\n * This file is part of PHP CS Fixer.\n * (c) kcloze <pei.greet@qq.com>\n * This source file is subject to the M"
  }
]

About this extraction

This page contains the full source code of the kcloze/multiprocess GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (49.5 KB), approximately 14.2k tokens, and a symbol index with 59 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!